commit
360a46e09b
15 changed files with 144 additions and 47 deletions
|
@ -556,17 +556,41 @@ function to_shorthand_int(int $int): string
|
|||
return (string)$int;
|
||||
}
|
||||
}
|
||||
|
||||
const TIME_UNITS = ["s"=>60,"m"=>60,"h"=>24,"d"=>365,"y"=>PHP_INT_MAX];
|
||||
function format_milliseconds(int $input): string
|
||||
abstract class TIME_UNITS
|
||||
{
|
||||
public const MILLISECONDS = "ms";
|
||||
public const SECONDS = "s";
|
||||
public const MINUTES = "m";
|
||||
public const HOURS = "h";
|
||||
public const DAYS = "d";
|
||||
public const YEARS = "y";
|
||||
public const CONVERSION = [
|
||||
self::MILLISECONDS=>1000,
|
||||
self::SECONDS=>60,
|
||||
self::MINUTES=>60,
|
||||
self::HOURS=>24,
|
||||
self::DAYS=>365,
|
||||
self::YEARS=>PHP_INT_MAX
|
||||
];
|
||||
}
|
||||
function format_milliseconds(int $input, string $min_unit = TIME_UNITS::SECONDS): string
|
||||
{
|
||||
$output = "";
|
||||
|
||||
$remainder = floor($input / 1000);
|
||||
$remainder = $input;
|
||||
|
||||
foreach (TIME_UNITS as $unit=>$conversion) {
|
||||
$found = false;
|
||||
|
||||
foreach (TIME_UNITS::CONVERSION as $unit=>$conversion) {
|
||||
$count = $remainder % $conversion;
|
||||
$remainder = floor($remainder / $conversion);
|
||||
|
||||
if ($found||$unit==$min_unit) {
|
||||
$found = true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($count==0&&$remainder<1) {
|
||||
break;
|
||||
}
|
||||
|
@ -575,6 +599,32 @@ function format_milliseconds(int $input): string
|
|||
|
||||
return trim($output);
|
||||
}
|
||||
function parse_to_milliseconds(string $input): int
|
||||
{
|
||||
$output = 0;
|
||||
$current_multiplier = 1;
|
||||
|
||||
if (preg_match('/^([0-9]+)$/i', $input, $match)) {
|
||||
// If just a number, then we treat it as milliseconds
|
||||
$length = $match[0];
|
||||
if (is_numeric($length)) {
|
||||
$length = floatval($length);
|
||||
$output += $length;
|
||||
}
|
||||
} else {
|
||||
foreach (TIME_UNITS::CONVERSION as $unit=>$conversion) {
|
||||
if (preg_match('/([0-9]+)'.$unit.'/i', $input, $match)) {
|
||||
$length = $match[1];
|
||||
if (is_numeric($length)) {
|
||||
$length = floatval($length);
|
||||
$output += $length * $current_multiplier;
|
||||
}
|
||||
}
|
||||
$current_multiplier *= $conversion;
|
||||
}
|
||||
}
|
||||
return intval($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a date into a time, a date, an "X minutes ago...", etc
|
||||
|
|
|
@ -105,6 +105,13 @@ class PolyfillsTest extends TestCase
|
|||
$this->assertEquals("1y 213d 16h 53m 20s", format_milliseconds(50000000000));
|
||||
}
|
||||
|
||||
public function test_parse_to_milliseconds()
|
||||
{
|
||||
$this->assertEquals(10, parse_to_milliseconds("10"));
|
||||
$this->assertEquals(5000, parse_to_milliseconds("5s"));
|
||||
$this->assertEquals(50000000000, parse_to_milliseconds("1y 213d 16h 53m 20s"));
|
||||
}
|
||||
|
||||
public function test_autodate()
|
||||
{
|
||||
$this->assertEquals(
|
||||
|
|
|
@ -462,6 +462,9 @@ class CronUploader extends Extension
|
|||
|
||||
// Generate info message
|
||||
if ($event->image_id == -1) {
|
||||
if(array_key_exists("mime",$event->metadata)) {
|
||||
throw new UploadException("File type not recognised (".$event->metadata["mime"]."). Filename: {$filename}");
|
||||
}
|
||||
throw new UploadException("File type not recognised. Filename: {$filename}");
|
||||
} elseif ($event->merged === true) {
|
||||
$infomsg = "Image merged. ID: {$event->image_id} - Filename: {$filename}";
|
||||
|
|
|
@ -5,7 +5,7 @@ class FeaturedInfo extends ExtensionInfo
|
|||
public const KEY = "featured";
|
||||
|
||||
public $key = self::KEY;
|
||||
public $name = "Featured Image";
|
||||
public $name = "Featured Post";
|
||||
public $url = self::SHIMMIE_URL;
|
||||
public $authors = self::SHISH_AUTHOR;
|
||||
public $license = self::LICENSE_GPLV2;
|
||||
|
@ -15,9 +15,9 @@ class FeaturedInfo extends ExtensionInfo
|
|||
to the other image control buttons (delete, rotate, etc).
|
||||
Clicking it will set the image as the site's current feature,
|
||||
which will be shown in the side bar of the post list.
|
||||
<p><b>Viewing a featured image</b>
|
||||
<p><b>Viewing a featured post</b>
|
||||
<br>Visit <code>/featured_image/view</code>
|
||||
<p><b>Downloading a featured image</b>
|
||||
<p><b>Downloading a featured post</b>
|
||||
<br>Link to <code>/featured_image/download</code>. This will give
|
||||
the raw data for an image (no HTML). This is useful so that you
|
||||
can set your desktop wallpaper to be the download URL, refreshed
|
||||
|
|
|
@ -20,7 +20,7 @@ class Featured extends Extension
|
|||
$id = int_escape($_POST['image_id']);
|
||||
if ($id > 0) {
|
||||
$config->set_int("featured_id", $id);
|
||||
log_info("featured", "Featured image set to >>$id", "Featured image set");
|
||||
log_info("featured", "Featured post set to >>$id", "Featured post set");
|
||||
$page->set_mode(PageMode::REDIRECT);
|
||||
$page->set_redirect(make_link("post/view/$id"));
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class FeaturedTest extends ShimmiePHPUnitTestCase
|
|||
$config->set_int("featured_id", $image_id);
|
||||
|
||||
$this->get_page("post/list");
|
||||
$this->assert_text("Featured Image");
|
||||
$this->assert_text("Featured Post");
|
||||
|
||||
# FIXME: test changing from one feature to another
|
||||
|
||||
|
@ -31,6 +31,6 @@ class FeaturedTest extends ShimmiePHPUnitTestCase
|
|||
// after deletion, there should be no feature
|
||||
$this->delete_image($image_id);
|
||||
$this->get_page("post/list");
|
||||
$this->assert_no_text("Featured Image");
|
||||
$this->assert_no_text("Featured Post");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ class FeaturedTheme extends Themelet
|
|||
{
|
||||
public function display_featured(Page $page, Image $image): void
|
||||
{
|
||||
$page->add_block(new Block("Featured Image", $this->build_featured_html($image), "left", 3));
|
||||
$page->add_block(new Block("Featured Post", $this->build_featured_html($image), "left", 3));
|
||||
}
|
||||
|
||||
public function get_buttons_html(int $image_id): string
|
||||
|
|
|
@ -60,7 +60,7 @@ class VideoFileHandlerTheme extends Themelet
|
|||
|
||||
$html .= "
|
||||
<video controls class='shm-main-image' id='main_image' alt='main image' poster='$thumb_url' {$autoplay} {$loop} {$mute}
|
||||
style='height: $height; width: $width; max-width: 100%; object-fit: cover;'>
|
||||
style='height: $height; width: $width; max-width: 100%; object-fit: contain; background-color: black;'>
|
||||
<source src='{$ilink}' type='{$mime}'>
|
||||
|
||||
<!-- If browser doesn't support filetype, fallback to flash -->
|
||||
|
|
|
@ -231,6 +231,10 @@ class Index extends Extension
|
|||
} elseif (preg_match("/^height([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
|
||||
$cmp = ltrim($matches[1], ":") ?: "=";
|
||||
$event->add_querylet(new Querylet("height $cmp :height{$event->id}", ["height{$event->id}"=>int_escape($matches[2])]));
|
||||
} elseif (preg_match("/^length([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(.+)$/i", $event->term, $matches)) {
|
||||
$value = parse_to_milliseconds($matches[2]);
|
||||
$cmp = ltrim($matches[1], ":") ?: "=";
|
||||
$event->add_querylet(new Querylet("length $cmp :length{$event->id}", ["length{$event->id}"=>$value]));
|
||||
} elseif (preg_match("/^order[=|:](id|width|height|length|filesize|filename)[_]?(desc|asc)?$/i", $event->term, $matches)) {
|
||||
$ord = strtolower($matches[1]);
|
||||
$default_order_for_column = preg_match("/^(id|filename)$/", $matches[1]) ? "ASC" : "DESC";
|
||||
|
|
|
@ -47,7 +47,7 @@ and of course start organising your images :-)
|
|||
if (count($images) > 0) {
|
||||
$this->display_page_images($page, $images);
|
||||
} else {
|
||||
$this->display_error(404, "No Images Found", "No images were found to match the search criteria");
|
||||
$this->display_error(404, "No posts Found", "No posts were found to match the search criteria");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,94 +181,94 @@ and of course start organising your images :-)
|
|||
|
||||
public function get_help_html()
|
||||
{
|
||||
return '<p>Searching is largely based on tags, with a number of special keywords available that allow searching based on properties of the images.</p>
|
||||
return '<p>Searching is largely based on tags, with a number of special keywords available that allow searching based on properties of the posts.</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tagname</pre>
|
||||
<p>Returns images that are tagged with "tagname".</p>
|
||||
<p>Returns posts that are tagged with "tagname".</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tagname othertagname</pre>
|
||||
<p>Returns images that are tagged with "tagname" and "othertagname".</p>
|
||||
<p>Returns posts that are tagged with "tagname" and "othertagname".</p>
|
||||
</div>
|
||||
|
||||
<p>Most tags and keywords can be prefaced with a negative sign (-) to indicate that you want to search for images that do not match something.</p>
|
||||
<p>Most tags and keywords can be prefaced with a negative sign (-) to indicate that you want to search for posts that do not match something.</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>-tagname</pre>
|
||||
<p>Returns images that are not tagged with "tagname".</p>
|
||||
<p>Returns posts that are not tagged with "tagname".</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>-tagname -othertagname</pre>
|
||||
<p>Returns images that are not tagged with "tagname" and "othertagname". This is different than without the negative sign, as images with "tagname" or "othertagname" can still be returned as long as the other one is not present.</p>
|
||||
<p>Returns posts that are not tagged with "tagname" and "othertagname". This is different than without the negative sign, as posts with "tagname" or "othertagname" can still be returned as long as the other one is not present.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tagname -othertagname</pre>
|
||||
<p>Returns images that are tagged with "tagname", but are not tagged with "othertagname".</p>
|
||||
<p>Returns posts that are tagged with "tagname", but are not tagged with "othertagname".</p>
|
||||
</div>
|
||||
|
||||
<p>Wildcard searches are possible as well using * for "any one, more, or none" and ? for "any one".</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tagn*</pre>
|
||||
<p>Returns images that are tagged with "tagname", "tagnot", or anything else that starts with "tagn".</p>
|
||||
<p>Returns posts that are tagged with "tagname", "tagnot", or anything else that starts with "tagn".</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tagn?me</pre>
|
||||
<p>Returns images that are tagged with "tagname", "tagnome", or anything else that starts with "tagn", has one character, and ends with "me".</p>
|
||||
<p>Returns posts that are tagged with "tagname", "tagnome", or anything else that starts with "tagn", has one character, and ends with "me".</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tags=1</pre>
|
||||
<p>Returns images with exactly 1 tag.</p>
|
||||
<p>Returns posts with exactly 1 tag.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>tags>0</pre>
|
||||
<p>Returns images with 1 or more tags. </p>
|
||||
<p>Returns posts with 1 or more tags. </p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for images by aspect ratio</p>
|
||||
<p>Search for posts by aspect ratio</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>ratio=4:3</pre>
|
||||
<p>Returns images with an aspect ratio of 4:3.</p>
|
||||
<p>Returns posts with an aspect ratio of 4:3.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>ratio>16:9</pre>
|
||||
<p>Returns images with an aspect ratio greater than 16:9. </p>
|
||||
<p>Returns posts with an aspect ratio greater than 16:9. </p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =. The relation is calculated by dividing width by height.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for images by file size</p>
|
||||
<p>Search for posts by file size</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>filesize=1</pre>
|
||||
<p>Returns images exactly 1 byte in size.</p>
|
||||
<p>Returns posts exactly 1 byte in size.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>filesize>100mb</pre>
|
||||
<p>Returns images greater than 100 megabytes in size. </p>
|
||||
<p>Returns posts greater than 100 megabytes in size. </p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =. Supported suffixes are kb, mb, and gb. Uses multiples of 1024.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for images by MD5 hash</p>
|
||||
<p>Search for posts by MD5 hash</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>hash=0D3512CAA964B2BA5D7851AF5951F33B</pre>
|
||||
|
@ -277,65 +277,86 @@ and of course start organising your images :-)
|
|||
|
||||
<hr/>
|
||||
|
||||
<p>Search for images by file name</p>
|
||||
<p>Search for posts by file name</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>filename=picasso.jpg</pre>
|
||||
<p>Returns images that are named "picasso.jpg".</p>
|
||||
<p>Returns posts that are named "picasso.jpg".</p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for images by source</p>
|
||||
<p>Search for posts by source</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>source=http://google.com/</pre>
|
||||
<p>Returns images with a source of "http://google.com/".</p>
|
||||
<p>Returns posts with a source of "http://google.com/".</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>source=any</pre>
|
||||
<p>Returns images with a source set.</p>
|
||||
<p>Returns posts with a source set.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>source=none</pre>
|
||||
<p>Returns images without a source set.</p>
|
||||
<p>Returns posts without a source set.</p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for images by date posted.</p>
|
||||
<p>Search for posts by date posted.</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>posted>=07-19-2019</pre>
|
||||
<p>Returns images posted on or after 07-19-2019.</p>
|
||||
<p>Returns posts posted on or after 07-19-2019.</p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =. Date format is mm-dd-yyyy. Date posted includes time component, so = will not work unless the time is exact.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for images by image dimensions</p>
|
||||
<p>Search for posts by length.</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>length>=1h</pre>
|
||||
<p>Returns posts that are longer than an hour.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>length<=10h15m</pre>
|
||||
<p>Returns posts that are shorter than 10 hours and 15 minutes.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>length>=10000</pre>
|
||||
<p>Returns posts that are longer than 10,000 milliseconds, or 10 seconds.</p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =. Available suffixes are ms, s, m, h, d, and y. A number by itself will be interpreted as milliseconds. Searches using = are not likely to work unless time is specified down to the millisecond.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>Search for posts by dimensions</p>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>size=640x480</pre>
|
||||
<p>Returns images exactly 640 pixels wide by 480 pixels high.</p>
|
||||
<p>Returns posts exactly 640 pixels wide by 480 pixels high.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>size>1920x1080</pre>
|
||||
<p>Returns images with a width larger than 1920 and a height larger than 1080.</p>
|
||||
<p>Returns posts with a width larger than 1920 and a height larger than 1080.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>width=1000</pre>
|
||||
<p>Returns images exactly 1000 pixels wide.</p>
|
||||
<p>Returns posts exactly 1000 pixels wide.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>height=1000</pre>
|
||||
<p>Returns images exactly 1000 pixels high.</p>
|
||||
<p>Returns posts exactly 1000 pixels high.</p>
|
||||
</div>
|
||||
|
||||
<p>Can use <, <=, >, >=, or =.</p>
|
||||
|
@ -346,12 +367,12 @@ and of course start organising your images :-)
|
|||
|
||||
<div class="command_example">
|
||||
<pre>order:id_asc</pre>
|
||||
<p>Returns images sorted by ID, smallest first.</p>
|
||||
<p>Returns posts sorted by ID, smallest first.</p>
|
||||
</div>
|
||||
|
||||
<div class="command_example">
|
||||
<pre>order:width_desc</pre>
|
||||
<p>Returns images sorted by width, largest first.</p>
|
||||
<p>Returns posts sorted by width, largest first.</p>
|
||||
</div>
|
||||
|
||||
<p>These fields are supported:
|
||||
|
|
|
@ -55,6 +55,7 @@ abstract class MediaEngine
|
|||
MimeType::GIF,
|
||||
MimeType::JPEG,
|
||||
MimeType::PNG,
|
||||
MimeType::PPM,
|
||||
MimeType::PSD,
|
||||
MimeType::TIFF,
|
||||
MimeType::WEBP,
|
||||
|
|
|
@ -46,6 +46,7 @@ class FileExtension
|
|||
public const PHP5 = 'php5';
|
||||
public const PNG = 'png';
|
||||
public const PSD = 'psd';
|
||||
public const PPM = 'ppm';
|
||||
public const MOV = 'mov';
|
||||
public const RSS = 'rss';
|
||||
public const SVG = 'svg';
|
||||
|
|
|
@ -144,6 +144,11 @@ class MimeMap
|
|||
self::MAP_EXT => [FileExtension::PNG],
|
||||
self::MAP_MIME => [MimeType::PNG],
|
||||
],
|
||||
MimeType::PPM => [
|
||||
self::MAP_NAME => "Portable Pixel Map",
|
||||
self::MAP_EXT => [FileExtension::PPM],
|
||||
self::MAP_MIME => [MimeType::PPM],
|
||||
],
|
||||
MimeType::PSD => [
|
||||
self::MAP_NAME => "PSD",
|
||||
self::MAP_EXT => [FileExtension::PSD],
|
||||
|
|
|
@ -38,6 +38,7 @@ class MimeType
|
|||
public const PDF = 'application/pdf';
|
||||
public const PHP = 'text/x-php';
|
||||
public const PNG = 'image/png';
|
||||
public const PPM = 'image/x-portable-pixmap';
|
||||
public const PSD = 'image/vnd.adobe.photoshop';
|
||||
public const QUICKTIME = 'video/quicktime';
|
||||
public const RSS = 'application/rss+xml';
|
||||
|
@ -242,6 +243,9 @@ class MimeType
|
|||
case FileExtension::ANI:
|
||||
$output = MimeType::ANI;
|
||||
break;
|
||||
case FileExtension::PPM:
|
||||
$output = MimeType::PPM;
|
||||
break;
|
||||
// TODO: There is no uniquely defined Mime type for the cursor format. Need to figure this out.
|
||||
// case FileExtension::CUR:
|
||||
// $output = MimeType::CUR;
|
||||
|
|
|
@ -22,6 +22,7 @@ class TranscodeImage extends Extension
|
|||
"ICO" => MimeType::ICO,
|
||||
"JPG" => MimeType::JPEG,
|
||||
"PNG" => MimeType::PNG,
|
||||
"PPM" => MimeType::PPM,
|
||||
"PSD" => MimeType::PSD,
|
||||
"TIFF" => MimeType::TIFF,
|
||||
"WEBP" => MimeType::WEBP
|
||||
|
|
Reference in a new issue