...
 
Commits (55)
zanata.xml merge=ours
......@@ -13,5 +13,5 @@ themes/*
themes/default/templates/data.html.ep
themes/default/templates/raw.html.ep
themes/default/templates/stats.json.ep
themes/default/public/packed/*
tmp/*
.zanata-cache/*
......@@ -29,6 +29,7 @@ sqlite2:
script:
- make podcheck
- MOJO_CONFIG=t/sqlite2.conf make minion &
- sleep 3
- MOJO_CONFIG=t/sqlite2.conf make test-sqlite
- MOJO_CONFIG=t/sqlite2.conf make watch
- MOJO_CONFIG=t/sqlite2.conf make cleanbdd
......
......@@ -2,7 +2,7 @@
## Main developers
* Luc Didry, aka Sky (<http://www.fiat-tux.fr>), core developer, @framasky on [Twitter](https://twitter.com/framasky) and on [Diaspora*](https://framasphere.org/public/framasky)
* Luc Didry, aka Sky (<http://www.fiat-tux.fr>), core developer, @framasky on [Mastodon](https://framapiaf.org/@framasky) and on [Diaspora*](https://framasphere.org/public/framasky)
* Dattaz (<http://dattaz.fr>), webapp developer, [@dat_taz](https://twitter.com/dat_taz)
## Contributors
......@@ -15,3 +15,4 @@
* Thor77 (<http://thor77.org>), german translation, among other things
* Quentin Pagès, occitan translation
* Alexis Clairet (<https://github.com/Turboconnard>), paste image to upload ability
* ButterflyOfFire (<https://mastodon.tetaneutral.net/@BoF>), arabic translation
Revision history for Lutim
0.9.2 2018-03-09
- Fix langage drop-down
- Allow to use HTML in broadcast message
- Remove old targets from Makefile
0.9.1 2018-03-09
- Fix default setting bug (db_path)
0.9.0 2018-03-09
- Added partial arabic translation (thx to ButterflyOfFire)
- Default theme is now non-fluid (ie don't take all the width of the screen)
- Use Photoswipe for the gallery instead of Unite gallery
- Use Zanata for translations (https://trad.framasoft.org)
- Add an option to personnalize proposed retention delays
- Use random Initialization Vector for encryption
- Use Mojo::SQLite instead of ORLite
- Fix various bugs
0.8.8 2018-02-07
- Fix security issues, thanks to SecuNinja
0.8.7 2017-12-22
- Fix bug if dbtype not configured in lutim.conf
0.8.6 2017-11-18
- Fix bug resulting in no EXIF tags deletion
0.8.5 2017-07-09
- Fix Henny Penny font path in css
0.8.4 2017-06-24
- Mitigate a bug using the same empty record twice
0.8.3 2017-06-15
- Fix the donuts charts in the /stats page.
0.8.2 2017-06-14
- Enforce Mojolicious::Plugin::AssetPack version
0.8.1 2017-06-13
- Fix #46
0.8 2017-06-13
- Improve statistics page
- Add database abstraction layer (#42)
......
EXTRACTDIR=-D lib -D themes/default/templates
EN=themes/default/lib/Lutim/I18N/en.po
FR=themes/default/lib/Lutim/I18N/fr.po
DE=themes/default/lib/Lutim/I18N/de.po
ES=themes/default/lib/Lutim/I18N/es.po
OC=themes/default/lib/Lutim/I18N/oc.po
XGETTEXT=carton exec local/bin/xgettext.pl
POT=themes/default/lib/Lutim/I18N/lutim.pot
XGETTEXT=carton exec local/bin/xgettext.pl -u
CARTON=carton exec
LUTIM=script/lutim
REAL_LUTIM=script/application
locales:
$(XGETTEXT) $(EXTRACTDIR) -o $(EN) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(FR) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(DE) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(ES) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(OC) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(POT) 2>/dev/null
push-locales: locales
zanata-cli -q -B push
pull-locales:
zanata-cli -q -B pull
stats-locales:
zanata-cli -q stats
podcheck:
podchecker lib/Lutim/DB/Image.pm
......@@ -30,16 +31,13 @@ test: podcheck test-sqlite test-pg
clean:
rm -rf lutim.db files/
rmassets:
rm -rf themes/default/public/packed/*
dev: rmassets
dev:
$(CARTON) morbo $(LUTIM) --listen http://0.0.0.0:3000 --watch lib/ --watch script/ --watch themes/ --watch lutim.conf
devlog:
multitail log/development.log
prod: rmassets
prod:
$(CARTON) hypnotoad -f $(LUTIM)
prodlog:
......
......@@ -64,6 +64,6 @@ It uses:
* [Moment.js](http://momentjs.com/) for displaying real dates instead of unix timestamps.
* [Fontello](http://fontello.com/) and the [markdown font](https://github.com/dcurtis/markdown-mark/) for the icons, licenses for the fontello icons fonts are in `public/font/LICENSE.txt`
* [Henny Penny](https://www.google.com/fonts/specimen/Henny+Penny) font designed by Olga Umpeleva for [Brownfox](http://brownfox.org)
* [Unite gallery](http://unitegallery.net/) for the gallery
* [PhotoSwipe](http://photoswipe.com/) for the gallery
* [JSZip](https://stuk.github.io/jszip/) for generating a zip containing all the images in the gallery
* [FileSaver.js](https://github.com/eligrey/FileSaver.js/) for saving the zip
requires 'Mojolicious', '>= 7.31';
requires 'EV';
requires 'IO::Socket::SSL';
requires 'Net::SSLeay', '>= 1.81';
requires 'Data::Validate::URI';
requires 'Net::Domain::TLD', '>= 1.73'; # Must have the last version to handle (at least) .xyz and .link
requires 'Mojo::Pg';
requires 'Mojo::SQLite';
requires 'Mojolicious::Plugin::I18N';
requires 'Mojolicious::Plugin::AssetPack';
requires 'Mojolicious::Plugin::DebugDumperHelper';
requires 'Mojolicious::Plugin::PgURLHelper';
requires 'Mojolicious::Plugin::StaticCache';
requires "Minion", "== 4.06";
requires 'Minion::Backend::SQLite', "== 0.009";
requires 'CSS::Minifier::XS';
requires 'JavaScript::Minifier::XS';
requires 'ORLite';
requires 'Text::Unidecode';
requires 'DateTime';
requires 'Filesys::DiskUsage';
......
......@@ -35,6 +35,7 @@ sub startup {
tweet_card_via => '@framasky',
max_file_size => 10*1024*1024,
https => 0,
proposed_delays => '0,1,7,30,365',
default_delay => 0,
max_delay => 0,
token_length => 24,
......@@ -42,8 +43,10 @@ sub startup {
thumbnail_size => 100,
theme => 'default',
dbtype => 'sqlite',
db_path => 'lutim.db',
max_files_in_zip => 15,
minion => {
prefix => '/',
minion => {
enabled => 0,
dbtype => 'sqlite',
db_path => 'minion.db'
......@@ -67,16 +70,18 @@ sub startup {
}
push @{$self->renderer->paths}, $self->home->rel_file('themes/default/templates');
push @{$self->static->paths}, $self->home->rel_file('themes/default/public');
# Internationalization
my $lib = $self->home->rel_file('themes/'.$config->{theme}.'/lib');
eval qq(use lib "$lib");
$self->plugin('I18N');
# Compressed assets
$self->plugin('AssetPack' => { pipes => [qw(Combine)] });
# Cache static files
$self->plugin('StaticCache');
# Helpers
$self->plugin('Lutim::Plugin::Helpers');
$self->plugin('Lutim::Plugin::Lang');
# Minion
if ($config->{minion}->{enabled}) {
......@@ -136,20 +141,6 @@ sub startup {
delete @{$wait_for_it}{grep { time - $wait_for_it->{$_} > $c->config->{anti_flood_delay} } keys %{$wait_for_it}} if (defined($wait_for_it));
}
);
$self->hook(after_static => sub {
my $c = shift;
$c->res->headers->cache_control('max-age=2592000, must-revalidate');
});
$self->asset->store->paths($self->static->paths);
$self->asset->process('index.css' => ('css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/animation.css', 'css/uploader.css', 'css/hennypenny.css', 'css/lutim.css', 'css/markdown.css'));
$self->asset->process('stats.css' => ('css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/morris-0.4.3.min.css', 'css/hennypenny.css', 'css/lutim.css'));
$self->asset->process('about.css' => ('css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/hennypenny.css', 'css/lutim.css'));
$self->asset->process('gallery.css' => ('/gallery/css/unite-gallery.css', '/gallery/themes/default/ug-theme-default.css'));
$self->asset->process('index.js' => ('js/bootstrap.min.js', 'js/lutim.js', 'js/dmuploader.min.js'));
$self->asset->process('stats.js' => ('js/bootstrap.min.js', 'js/lutim.js', 'js/raphael-min.js', 'js/morris-0.4.3.min.js', 'js/stats.js'));
$self->asset->process('freeze.js' => ('js/jquery-2.1.0.min.js', 'js/freezeframe.min.js'));
$self->defaults(layout => 'default');
......@@ -180,6 +171,10 @@ sub startup {
to('Controller#stats')->
name('stats');
$r->get('/lang/:l')->
to('Controller#change_lang')->
name('lang');
$r->get('/partial/:file' => sub {
my $c = shift;
$c->render(
......@@ -230,14 +225,24 @@ sub startup {
to('Controller#get_counter')->
name('counter');
$r->get('/about/(:short).(:f)')->
to('Controller#about_img')->
name('about_img');
$r->get('/about/:short/(:key).(:f)')->
to('Controller#about_img')->
name('about_img');
$r->get('/(:short).(:f)')->
to('Controller#short')->
name('short');
$r->get('/:short')->
to('Controller#short');
$r->get('/:short/(:key).(:f)')->
to('Controller#short');
$r->get('/:short/:key')->
to('Controller#short');
}
......
......@@ -152,7 +152,7 @@ sub run {
% l('Total')
% );
<script>
Morris.Donut({
var enabled_donut = {
element: 'raw-enabled-holder',
data: [
{label: "<%= l('no time limit') %>", value: $unlimited_enabled},
......@@ -168,8 +168,8 @@ Morris.Donut({
'#427dc1',
'#455ac3',
]
});
Morris.Donut({
};
var disabled_donut = {
element: 'raw-disabled-holder',
data: [
{label: "<%= l('no time limit') %>", value: $unlimited_disabled},
......@@ -185,7 +185,7 @@ Morris.Donut({
'#427dc1',
'#455ac3',
]
});
};
</script>
$dom2
EOF
......
......@@ -41,10 +41,16 @@ use Locale::Maketext::Lexicon {
_style => 'gettext',
'*' => [
Gettext => dirname(__FILE__) . '/I18N/*.po',
Gettext => dirname(__FILE__) . '/../../../default/lib/Lutim/I18N/*.po',
Gettext => $app_dir . 'themes/default/lib/Lutim/I18N/*.po',
]
};
use vars qw($app_dir);
BEGIN {
use Cwd;
my $app_dir = getcwd;
}
1;
EOF
......@@ -58,6 +64,7 @@ FR=lib/Lutim/I18N/fr.po
DE=lib/Lutim/I18N/de.po
ES=lib/Lutim/I18N/es.po
OC=lib/Lutim/I18N/oc.po
AR=lib/Lutim/I18N/ar.po
SEDOPTS=-e "s\@SOME DESCRIPTIVE TITLE\@Lutim language file\@" \\
-e "s\@YEAR THE PACKAGE'S COPYRIGHT HOLDER\@2015 Luc Didry\@" \\
-e "s\@CHARSET\@utf8\@" \\
......@@ -74,6 +81,7 @@ locales:
\$(XGETTEXT) -D templates -D ../default/templates -o \$(DE) 2>/dev/null
\$(XGETTEXT) -D templates -D ../default/templates -o \$(ES) 2>/dev/null
\$(XGETTEXT) -D templates -D ../default/templates -o \$(OC) 2>/dev/null
\$(XGETTEXT) -D templates -D ../default/templates -o \$(AR) 2>/dev/null
sed \$(SEDOPTS) -i \$(EN)
sed \$(SEDOPTS2) -i \$(EN)
sed \$(SEDOPTS) -i \$(FR)
......@@ -83,7 +91,9 @@ locales:
sed \$(SEDOPTS) -i \$(ES)
sed \$(SEDOPTS2) -i \$(ES)
sed \$(SEDOPTS) -i \$(OC)
sed \$(SEDOPTS2) -i \$(OC)
sed \$(SEDOPTS) -i \$(OC)
sed \$(SEDOPTS2) -i \$(AR)
sed \$(SEDOPTS2) -i \$(AR)
EOF
open $f, '>', File::Spec->catfile($home, 'Makefile') or die "Unable to open $home/Makefile: $!";
......
# vim:set sw=4 ts=4 sts=4 expandtab:
package Lutim::Controller;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::Util qw(url_escape url_unescape b64_encode);
use Mojo::Util qw(url_escape url_unescape b64_encode encode);
use Mojo::Asset::Memory;
use Mojo::JSON qw(true false);
use Lutim::DB::Image;
......@@ -45,6 +45,19 @@ sub about {
shift->render(template => 'about');
}
sub change_lang {
my $c = shift;
my $l = $c->param('l');
$c->cookie(lutim_lang => $l, { path => $c->config('prefix') });
if ($c->req->headers->referrer) {
return $c->redirect_to($c->req->headers->referrer);
} else {
return $c->redirect_to('/');
}
}
sub stats {
my $c = shift;
......@@ -71,6 +84,31 @@ sub infos {
);
}
sub about_img {
my $c = shift;
my $short = $c->param('short');
my $image = Lutim::DB::Image->new(app => $c->app, short => $short);
if ($image->enabled && $image->path) {
return $c->render(
json => {
success => true,
data => {
width => $image->width,
height => $image->height,
}
}
);
} else {
return $c->render(
json => {
success => false,
msg => $c->l('Unable to find the image %1.', $short)
}
);
}
}
sub webapp {
my $c = shift;
......@@ -234,7 +272,7 @@ sub delete {
}
},
any => sub {
shift->render_not_found;
$c->helpers->reply->not_found;
}
);
}
......@@ -384,29 +422,31 @@ sub add {
}
unless ((defined($keep_exif) && $keep_exif) || $mediatype eq 'image/svg+xml' || $mediatype !~ m#image/(x-)?xcf# || $mediatype ne 'image/webp') {
# Remove the EXIF tags
my $data = new IO::Scalar \$upload->slurp();
my $et = new Image::ExifTool;
# Use $data in Image::ExifTool object
$et->ExtractInfo($data);
# Remove all metadata
$et->SetNewValue('*', undef);
# Create a temporary IO::Scalar to write into
my $temp;
my $a = new IO::Scalar \$temp;
$et->WriteInfo($data, $a);
# Update the uploaded file with it's no-tags clone
$data = Mojo::Asset::Memory->new->add_chunk($temp);
$upload->asset($data);
unless (defined($keep_exif) && $keep_exif) {
if ($mediatype ne 'image/svg+xml' && $mediatype !~ m#image/(x-)?xcf# && $mediatype ne 'image/webp') {
# Remove the EXIF tags
my $data = new IO::Scalar \$upload->slurp();
my $et = new Image::ExifTool;
# Use $data in Image::ExifTool object
$et->ExtractInfo($data);
# Remove all metadata
$et->SetNewValue('*', undef);
# Create a temporary IO::Scalar to write into
my $temp;
my $a = new IO::Scalar \$temp;
$et->WriteInfo($data, $a);
# Update the uploaded file with it's no-tags clone
$data = Mojo::Asset::Memory->new->add_chunk($temp);
$upload->asset($data);
}
}
my $key;
my ($key, $iv);
if ($c->param('crypt') || $c->config('always_encrypt')) {
($upload, $key) = $c->crypt($upload, $filename);
($upload, $key, $iv) = $c->crypt($upload, $filename);
}
$upload->move_to($path);
......@@ -421,6 +461,7 @@ sub add {
->created_by($ip)
->width($width)
->height($height)
->iv($iv)
->write;
# Log image creation
......@@ -512,7 +553,9 @@ sub short {
my $short = $c->param('short');
my $touit = $c->param('t');
my $key = $c->param('key');
my $thumb = $c->param('thumb');
my $thumb;
$thumb = '' if defined $c->param('thumb');
$thumb = $c->param('width') if defined $c->param('width');
my $dl = (defined($c->param('dl'))) ? 'attachment' : 'inline';
my $image = Lutim::DB::Image->new(app => $c->app, short => $short);
......@@ -532,7 +575,7 @@ sub short {
}
my $test;
if (defined($touit)) {
if (defined($touit) && $image->mediatype !~ m/svg/) {
$test = 1;
my $short = $image->short;
$short .= '/'.$key if (defined($key));
......@@ -575,12 +618,7 @@ sub short {
);
return $c->redirect_to('/');
} else {
my $expires = ($image->delete_at_day) ? $image->delete_at_day : 360;
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $image->created_at);
$dt->set_time_zone('GMT');
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
$test = $c->render_file($im_loaded, $image->filename, $image->path, $image->mediatype, $dl, $expires, $image->delete_at_first_view, $key, $thumb);
$test = $c->render_file($im_loaded, $image, $dl, $key, $thumb);
}
}
......@@ -618,7 +656,7 @@ sub short {
return $c->redirect_to('/');
} else {
# Image never existed
$c->render_not_found;
$c->helpers->reply->not_found;
}
}
......@@ -656,7 +694,7 @@ sub zip {
$c->delete_image($image);
# Warn user
$zip->addString($c->l('Unable to find the image: it has been deleted.'), 'images/'.$filename.'.txt');
$zip->addString(encode('UTF-8', $c->l('Unable to find the image: it has been deleted.')), 'images/'.$filename.'.txt');
next;
}
......@@ -668,7 +706,7 @@ sub zip {
# Delete image
$c->delete_image($image);
$zip->addString($c->l('Unable to find the image: it has been deleted.'), 'images/'.$filename.'.txt');
$zip->addString(encode('UTF-8', $c->l('Unable to find the image: it has been deleted.')), 'images/'.$filename.'.txt');
next;
} else {
my $expires = ($image->delete_at_day) ? $image->delete_at_day : 360;
......@@ -679,7 +717,7 @@ sub zip {
my $path = $image->path;
unless ( -f $path && -r $path ) {
$c->app->log->error("Cannot read file [$path]. error [$!]");
$zip->addString($c->l('Unable to find the image: it has been deleted.'), 'images/'.$filename.'.txt');
$zip->addString(encode('UTF-8', $c->l('Unable to find the image: it has been deleted.')), 'images/'.$filename.'.txt');
next;
}
......@@ -704,10 +742,10 @@ sub zip {
$c->app->log->info('[NOT FOUND] someone tried to view '.$short.' but it does\'nt exist anymore.');
# Warn user
$zip->addString($c->l('Unable to find the image: it has been deleted.'), 'images/'.$image->filename.'.txt');
$zip->addString(encode('UTF-8', $c->l('Unable to find the image: it has been deleted.')), 'images/'.$image->filename.'.txt');
next;
} else {
$zip->addString($c->l('Image not found.'), 'images/'.$short.'.txt');
$zip->addString(encode('UTF-8', $c->l('Image not found.')), 'images/'.$short.'.txt');
next;
}
}
......
......@@ -17,6 +17,7 @@ has 'last_access_at';
has 'mod_token';
has 'width';
has 'height';
has 'iv';
has 'app';
=head1 NAME
......@@ -64,6 +65,8 @@ Have a look at Lutim::DB::Image::SQLite's code: it's simple and may be more unde
=item B<height> : integer
=item B<iv> : initialization vector for the file encryption
=item B<app> : a mojolicious object
=back
......@@ -125,7 +128,8 @@ sub to_hash {
last_access_at => $c->last_access_at,
mod_token => $c->mod_token,
width => $c->width,
height => $c->height
height => $c->height,
height => $c->iv
};
}
......
......@@ -69,7 +69,8 @@ sub select_created_after {
sub select_empty {
my $c = shift;
my $record = $c->app->pg->db->query('SELECT * FROM lutim WHERE path IS NULL LIMIT 1')->hashes->first;
my $record = $c->app->pg->db->query('SELECT * FROM lutim WHERE path IS NULL')->hashes->shuffle->first;
$c->app->pg->db->query('UPDATE lutim SET path = ? WHERE short = ?', 'used', $record->{short});
$c = $c->_slurp($record);
......@@ -80,9 +81,9 @@ sub write {
my $c = shift;
if ($c->record) {
$c->app->pg->db->query('UPDATE lutim SET counter = ?, created_at = ?, created_by = ?, delete_at_day = ?, delete_at_first_view = ?, enabled = ?, filename = ?, footprint = ?, height = ?, last_access_at = ?, mediatype = ?, mod_token = ?, path = ?, short = ?, width = ? WHERE short = ?', $c->counter, $c->created_at, $c->created_by, $c->delete_at_day, $c->delete_at_first_view, $c->enabled, $c->filename, $c->footprint, $c->height, $c->last_access_at, $c->mediatype, $c->mod_token, $c->path, $c->short, $c->width, $c->short);
$c->app->pg->db->query('UPDATE lutim SET counter = ?, created_at = ?, created_by = ?, delete_at_day = ?, delete_at_first_view = ?, enabled = ?, filename = ?, footprint = ?, height = ?, last_access_at = ?, mediatype = ?, mod_token = ?, path = ?, short = ?, width = ?, iv = ? WHERE short = ?', $c->counter, $c->created_at, $c->created_by, $c->delete_at_day, $c->delete_at_first_view, $c->enabled, $c->filename, $c->footprint, $c->height, $c->last_access_at, $c->mediatype, $c->mod_token, $c->path, $c->short, $c->width, $c->iv, $c->short);
} else {
$c->app->pg->db->query('INSERT INTO lutim (counter, created_at, created_by, delete_at_day, delete_at_first_view, enabled, filename, footprint, height, last_access_at, mediatype, mod_token, path, short, width) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', $c->counter, $c->created_at, $c->created_by, $c->delete_at_day, $c->delete_at_first_view, $c->enabled, $c->filename, $c->footprint, $c->height, $c->last_access_at, $c->mediatype, $c->mod_token, $c->path, $c->short, $c->width);
$c->app->pg->db->query('INSERT INTO lutim (counter, created_at, created_by, delete_at_day, delete_at_first_view, enabled, filename, footprint, height, last_access_at, mediatype, mod_token, path, short, width, iv) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', $c->counter, $c->created_at, $c->created_by, $c->delete_at_day, $c->delete_at_first_view, $c->enabled, $c->filename, $c->footprint, $c->height, $c->last_access_at, $c->mediatype, $c->mod_token, $c->path, $c->short, $c->width, $c->iv);
$c->record(1);
}
......@@ -219,6 +220,7 @@ sub _slurp {
$c->mod_token($image->{mod_token});
$c->width($image->{width});
$c->height($image->{height});
$c->iv($image->{iv});
$c->record(1);
}
......
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::DB::SQLite;
use Mojolicious;
use Mojo::File;
use FindBin qw($Bin);
BEGIN {
my $m = Mojolicious->new;
my $cfile = Mojo::File->new($Bin, '..' , 'lutim.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
our $config = $m->plugin('Config' =>
{
file => $cfile->to_abs->to_string,
default => {
db_path => 'lutim.db'
}
}
);
}
# Create database
use ORLite {
file => $config->{db_path},
unicode => 1,
create => sub {
my $dbh = shift;
$dbh->do(
'CREATE TABLE lutim (
short TEXT PRIMARY KEY,
path TEXT,
footprint TEXT,
enabled INTEGER,
mediatype TEXT,
filename TEXT,
counter INTEGER,
delete_at_first_view INTEGER,
delete_at_day INTEGER,
created_at INTEGER,
created_by TEXT,
last_access_at INTEGER,
mod_token TEXT,
width INTEGER,
height INTEGER)'
);
return 1;
}
};
1;
......@@ -4,6 +4,7 @@ use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Util qw(quote);
use Crypt::CBC;
use Data::Entropy qw(entropy_source);
use DateTime;
sub register {
my ($self, $app) = @_;
......@@ -16,24 +17,36 @@ sub register {
# Database migration
my $migrations = Mojo::Pg::Migrations->new(pg => $app->pg);
if ($app->mode eq 'development') {
$migrations->from_file('utilities/migrations.sql')->migrate(0)->migrate(1);
if ($app->mode eq 'development' && $ENV{LUTIM_DEBUG}) {
$migrations->from_file('utilities/migrations/postgresql.sql')->migrate(0)->migrate(2);
} else {
$migrations->from_file('utilities/migrations.sql')->migrate(1);
$migrations->from_file('utilities/migrations/postgresql.sql')->migrate(2);
}
} elsif ($app->config('dbtype') eq 'sqlite') {
# SQLite database migration if needed
use Mojo::SQLite;
$app->helper(sqlite => \&_sqlite);
my $sql = Mojo::SQLite->new('sqlite:'.$app->config('db_path'));
my $migrations = $sql->migrations;
if ($app->mode eq 'development' && $ENV{LUTIM_DEBUG}) {
$migrations->from_file('utilities/migrations/sqlite.sql')->migrate(0)->migrate(2);
} else {
$migrations->from_file('utilities/migrations/sqlite.sql')->migrate(2);
}
}
$app->helper(render_file => \&_render_file);
$app->helper(ip => \&_ip);
$app->helper(provisioning => \&_provisioning);
$app->helper(shortener => \&_shortener);
$app->helper(stop_upload => \&_stop_upload);
$app->helper(max_delay => \&_max_delay);
$app->helper(default_delay => \&_default_delay);
$app->helper(is_selected => \&_is_selected);
$app->helper(crypt => \&_crypt);
$app->helper(decrypt => \&_decrypt);
$app->helper(delete_image => \&_delete_image);
$app->helper(render_file => \&_render_file);
$app->helper(ip => \&_ip);
$app->helper(provisioning => \&_provisioning);
$app->helper(shortener => \&_shortener);
$app->helper(stop_upload => \&_stop_upload);
$app->helper(max_delay => \&_max_delay);
$app->helper(default_delay => \&_default_delay);
$app->helper(is_selected => \&_is_selected);
$app->helper(crypt => \&_crypt);
$app->helper(decrypt => \&_decrypt);
$app->helper(delete_image => \&_delete_image);
}
sub _pg {
......@@ -43,9 +56,23 @@ sub _pg {
return $pg;
}
sub _sqlite {
my $c = shift;
state $sqlite = Mojo::SQLite->new('sqlite:'.$c->app->config('db_path'));
return $sqlite;
}
sub _render_file {
my $c = shift;
my ($im_loaded, $filename, $path, $mediatype, $dl, $expires, $nocache, $key, $thumb) = @_;
my ($im_loaded, $img, $dl, $key, $thumb) = @_;
my ($filename, $path, $iv, $mediatype, $no_cache) = ($img->filename, $img->path, $img->iv, $img->mediatype, $img->delete_at_first_view);
my $expires = ($img->delete_at_day) ? $img->delete_at_day : 360;
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $img->created_at);
$dt->set_time_zone('GMT');
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
$dl = 'attachment' if ($mediatype =~ m/svg/);
$filename = quote($filename);
......@@ -62,7 +89,7 @@ sub _render_file {
$mediatype =~ s/x-//;
my $headers = Mojo::Headers->new();
if ($nocache) {
if ($no_cache || defined($thumb)) {
$headers->add('Cache-Control' => 'no-cache, no-store, max-age=0, must-revalidate');
} else {
$headers->add('Expires' => $expires);
......@@ -72,7 +99,7 @@ sub _render_file {
$c->res->content->headers($headers);
if ($key) {
$asset = $c->decrypt($key, $path);
$asset = $c->decrypt($key, $path, $iv);
} else {
$asset = Mojo::Asset::File->new(path => $path);
}
......@@ -82,7 +109,11 @@ sub _render_file {
$im->BlobToImage($asset->slurp);
# Create the thumbnail
$im->Resize(geometry=>'x'.$c->config('thumbnail_size'));
if ($thumb eq '') {
$im->Resize(geometry => 'x'.$c->config('thumbnail_size'));
} else {
$im->Resize(geometry => $thumb);
}
# Replace the asset with the thumbnail
$asset = Mojo::Asset::Memory->new->add_chunk($im->ImageToBlob());
......@@ -187,12 +218,13 @@ sub _crypt {
my $filename = shift;
my $key = $c->shortener($c->config('crypto_key_length'));
my $iv = $c->shortener(8);
my $cipher = Crypt::CBC->new(
-key => $key,
-cipher => 'Blowfish',
-header => 'none',
-iv => 'dupajasi'
-iv => $iv
);
$cipher->start('encrypting');
......@@ -206,19 +238,21 @@ sub _crypt {
$crypt_upload->filename($filename);
$crypt_upload->asset($crypt_asset);
return ($crypt_upload, $key);
return ($crypt_upload, $key, $iv);
}
sub _decrypt {
my $c = shift;
my $key = shift;
my $file = shift;
my $iv = shift;
$iv = 'dupajasi' unless $iv;
my $cipher = Crypt::CBC->new(
-key => $key,
-cipher => 'Blowfish',
-header => 'none',
-iv => 'dupajasi'
-iv => $iv
);
$cipher->start('decrypting');
......
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::Plugin::Lang;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Collection;
use Mojo::File;
sub register {
my ($self, $app) = @_;
$app->helper(available_langs => \&_available_langs);
$app->hook(
before_dispatch => sub {
my $c = shift;
$c->languages($c->cookie('lutim_lang')) if $c->cookie('lutim_lang');
}
);
}
sub _available_langs {
my $c = shift;
state $langs = Mojo::Collection->new(
glob($c->app->home->rel_file('themes/'.$c->config('theme').'/lib/Lutim/I18N/*po')),
glob($c->app->home->rel_file('themes/default/lib/Lutim/I18N/*po'))
)->map(
sub {
Mojo::File->new($_)->basename('.po');
}
)->uniq->sort(
sub {
$c->l($a) cmp $c->l($b)
}
)->to_array;
}
1;
......@@ -22,14 +22,34 @@ sub startup {
{
file => $cfile,
default => {
prefix => '/',
theme => 'default',
provisioning => 100,
provis_step => 5,
length => 8,
always_encrypt => 0,
anti_flood_delay => 5,
tweet_card_via => '@framasky',
max_file_size => 10*1024*1024,
https => 0,
proposed_delays => '0,1,7,30,365',
default_delay => 0,
max_delay => 0,
token_length => 24,
crypto_key_length => 8,
thumbnail_size => 100,
theme => 'default',
dbtype => 'sqlite',
db_path => 'lutim.db',
max_files_in_zip => 15,
prefix => '/',
minion => {
enabled => 0,
dbtype => 'sqlite',
db_path => 'minion.db'
},
}
}
);
$self->plugin('Lutim::Plugin::Helpers');
$config->{prefix} = $config->{url_sub_dir} if (defined($config->{url_sub_dir}) && $config->{prefix} eq '/');
$self->app->log->warn('"url_sub_dir" configuration option is deprecated. Use "prefix" instead. "url_sub_dir" will be removed in the future') if (defined($config->{url_sub_dir}));
......@@ -42,10 +62,12 @@ sub startup {
}
push @{$self->static->paths}, $self->home->rel_file('themes/default/public');
$self->hook(after_static => sub {
my $c = shift;
$c->res->headers->cache_control('max-age=2592000, must-revalidate');
});
# Cache static files
$self->plugin('StaticCache');
# Helpers
$self->plugin('Lutim::Plugin::Helpers');
$self->plugin('Mount' => {$config->{prefix} => File::Spec->catfile($Bin, '..', 'script', 'application')});
}
......
......@@ -89,6 +89,10 @@
# optional, default is 0 (no limit)
#default_delay => 0,
# comma-separated values proposed for delays
# optional, default is '0,1,7,30,365'
#proposed_delays => '0,1,7,30,365',
# number of days after which the images will be deleted, even if they were uploaded with "no delay" (or value superior to max_delay)
# a warning message will be displayed on homepage
# optional, default is 0 (no limit)
......
#!/bin/bash
FILE=$1
if [[ ! -e themes/default/lib/Lutim/I18N/$FILE.po ]]
then
echo "themes/default/lib/Lutim/I18N/$FILE.po does not exist. Exiting."
exit 1
else
LOCALE=$(echo $FILE | sed -e "s@_@-@g")
zanata-cli -q -B push --push-type trans -l $LOCALE
fi
......@@ -118,7 +118,7 @@
# you can define it relative to lutim directory or set an absolute path
# remember that it has to be in a directory writable by Lutim user
# optional, default is lutim.db
db_path => 'testdqlite1.db',
db_path => 'testsqlite1.db',
# PostgreSQL ONLY - only used if dbtype is set to postgresql
# these are the credentials to access the PostgreSQL database
......
This source diff could not be displayed because it is too large. You can view the blob instead.
<svg width="264" height="88" viewBox="0 0 264 88" xmlns="http://www.w3.org/2000/svg"><title>default-skin 2</title><g fill="none" fill-rule="evenodd"><g><path d="M67.002 59.5v3.768c-6.307.84-9.184 5.75-10.002 9.732 2.22-2.83 5.564-5.098 10.002-5.098V71.5L73 65.585 67.002 59.5z" id="Shape" fill="#fff"/><g fill="#fff"><path d="M13 29v-5h2v3h3v2h-5zM13 15h5v2h-3v3h-2v-5zM31 15v5h-2v-3h-3v-2h5zM31 29h-5v-2h3v-3h2v5z" id="Shape"/></g><g fill="#fff"><path d="M62 24v5h-2v-3h-3v-2h5zM62 20h-5v-2h3v-3h2v5zM70 20v-5h2v3h3v2h-5zM70 24h5v2h-3v3h-2v-5z"/></g><path d="M20.586 66l-5.656-5.656 1.414-1.414L22 64.586l5.656-5.656 1.414 1.414L23.414 66l5.656 5.656-1.414 1.414L22 67.414l-5.656 5.656-1.414-1.414L20.586 66z" fill="#fff"/><path d="M111.785 65.03L110 63.5l3-3.5h-10v-2h10l-3-3.5 1.785-1.468L117 59l-5.215 6.03z" fill="#fff"/><path d="M152.215 65.03L154 63.5l-3-3.5h10v-2h-10l3-3.5-1.785-1.468L147 59l5.215 6.03z" fill="#fff"/><g><path id="Rectangle-11" fill="#fff" d="M160.957 28.543l-3.25-3.25-1.413 1.414 3.25 3.25z"/><path d="M152.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z" id="Oval-1" stroke="#fff" stroke-width="1.5"/><path fill="#fff" d="M150 21h5v1h-5z"/></g><g><path d="M116.957 28.543l-1.414 1.414-3.25-3.25 1.414-1.414 3.25 3.25z" fill="#fff"/><path d="M108.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z" stroke="#fff" stroke-width="1.5"/><path fill="#fff" d="M106 21h5v1h-5z"/><path fill="#fff" d="M109.043 19.008l-.085 5-1-.017.085-5z"/></g></g></g></svg>
\ No newline at end of file
......@@ -12,4 +12,5 @@
.icon-flattr:before { content: '\e80a'; } /* '' */
.icon-cancel:before { content: '\e80b'; } /* '' */
.icon-picture:before { content: '\e80c'; } /* '' */
.icon-file-archive:before { content: '\e80d'; } /* '' */
\ No newline at end of file
.icon-file-archive:before { content: '\e80d'; } /* '' */
.icon-language:before { content: '\e80e'; } /* '' */
\ No newline at end of file
......@@ -12,4 +12,5 @@
.icon-flattr { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }
.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }
.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }
.icon-file-archive { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
\ No newline at end of file
.icon-file-archive { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
.icon-language { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
\ No newline at end of file
......@@ -23,4 +23,5 @@
.icon-flattr { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }
.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }
.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }
.icon-file-archive { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
\ No newline at end of file
.icon-file-archive { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
.icon-language { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
\ No newline at end of file
@font-face {
font-family: 'fontello';
src: url('../font/fontello.eot?79499210');
src: url('../font/fontello.eot?79499210#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?79499210') format('woff2'),
url('../font/fontello.woff?79499210') format('woff'),
url('../font/fontello.ttf?79499210') format('truetype'),
url('../font/fontello.svg?79499210#fontello') format('svg');
src: url('../font/fontello.eot?84200725');
src: url('../font/fontello.eot?84200725#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?84200725') format('woff2'),
url('../font/fontello.woff?84200725') format('woff'),
url('../font/fontello.ttf?84200725') format('truetype'),
url('../font/fontello.svg?84200725#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
......@@ -15,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('../font/fontello.svg?79499210#fontello') format('svg');
src: url('../font/fontello.svg?84200725#fontello') format('svg');
}
}
*/
......@@ -68,4 +68,5 @@
.icon-flattr:before { content: '\e80a'; } /* '' */
.icon-cancel:before { content: '\e80b'; } /* '' */
.icon-picture:before { content: '\e80c'; } /* '' */
.icon-file-archive:before { content: '\e80d'; } /* '' */
\ No newline at end of file
.icon-file-archive:before { content: '\e80d'; } /* '' */
.icon-language:before { content: '\e80e'; } /* '' */
\ No newline at end of file
/* vim:set sw=4 ts=4 sts=4 ft=css expandtab: */
@media (max-width: 767px) {
body {
padding-top: 5px;
......@@ -7,7 +8,6 @@
@media (min-width: 768px) {
body {
padding-top: 40px;
padding-bottom: 40px;
}
}
......@@ -17,6 +17,10 @@
margin: 0 auto;
}
nav .container {
padding: 0;
}
.jsonly {
display: none;
}
......@@ -83,3 +87,20 @@ label.always-encrypt {
width: 0;
display: hidden;
}
.gallery figure {
margin-right: auto;
margin-left: auto;
margin-bottom: 5px;
}
@media (min-width: 1200px) {
.gallery { column-count: 4 }
}
@media (min-width: 991px) and (max-width: 1199px) {
.gallery { column-count: 3 }
}
@media (min-width: 676px) and (max-width: 990px) {
.gallery { column-count: 2 }
}
@media (max-width: 675px) {
.gallery { column-count: 1 }
}
/*! PhotoSwipe main CSS by Dmitry Semenov | photoswipe.com | MIT license */
/*
Styles for basic PhotoSwipe functionality (sliding area, open/close transitions)
*/
/* pswp = photoswipe */
.pswp {
display: none;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
overflow: hidden;
-ms-touch-action: none;
touch-action: none;
z-index: 1500;
-webkit-text-size-adjust: 100%;
/* create separate layer, to avoid paint on window.onscroll in webkit/blink */
-webkit-backface-visibility: hidden;
outline: none; }
.pswp * {
-webkit-box-sizing: border-box;
box-sizing: border-box; }
.pswp img {
max-width: none; }
/* style is added when JS option showHideOpacity is set to true */
.pswp--animate_opacity {
/* 0.001, because opacity:0 doesn't trigger Paint action, which causes lag at start of transition */
opacity: 0.001;
will-change: opacity;
/* for open/close transition */
-webkit-transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1); }
.pswp--open {
display: block; }
.pswp--zoom-allowed .pswp__img {
/* autoprefixer: off */
cursor: -webkit-zoom-in;
cursor: -moz-zoom-in;
cursor: zoom-in; }
.pswp--zoomed-in .pswp__img {
/* autoprefixer: off */
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab; }
.pswp--dragging .pswp__img {
/* autoprefixer: off */
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing; }
/*
Background is added as a separate element.
As animating opacity is much faster than animating rgba() background-color.
*/
.pswp__bg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #000;
opacity: 0;
-webkit-transform: translateZ(0);
transform: translateZ(0);
-webkit-backface-visibility: hidden;
will-change: opacity; }
.pswp__scroll-wrap {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: hidden; }
.pswp__container,
.pswp__zoom-wrap {
-ms-touch-action: none;
touch-action: none;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0; }
/* Prevent selection and tap highlights */
.pswp__container,
.pswp__img {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none; }
.pswp__zoom-wrap {
position: absolute;
width: 100%;
-webkit-transform-origin: left top;
-ms-transform-origin: left top;
transform-origin: left top;
/* for open/close transition */
-webkit-transition: -webkit-transform 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: transform 333ms cubic-bezier(0.4, 0, 0.22, 1); }
.pswp__bg {
will-change: opacity;
/* for open/close transition */
-webkit-transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1); }
.pswp--animated-in .pswp__bg,
.pswp--animated-in .pswp__zoom-wrap {
-webkit-transition: none;
transition: none; }
.pswp__container,
.pswp__zoom-wrap {
-webkit-backface-visibility: hidden; }
.pswp__item {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow: hidden; }
.pswp__img {
position: absolute;
width: auto;
height: auto;
top: 0;
left: 0; }
/*
stretched thumbnail or div placeholder element (see below)
style is added to avoid flickering in webkit/blink when layers overlap
*/
.pswp__img--placeholder {
-webkit-backface-visibility: hidden; }
/*
div element that matches size of large image
large image loads on top of it
*/
.pswp__img--placeholder--blank {
background: #222; }
.pswp--ie .pswp__img {
width: 100% !important;
height: auto !important;
left: 0;
top: 0; }
/*
Error message appears when image is not loaded
(JS option errorMsg controls markup)
*/
.pswp__error-msg {
position: absolute;
left: 0;
top: 50%;
width: 100%;
text-align: center;
font-size: 14px;
line-height: 16px;
margin-top: -8px;
color: #CCC; }
.pswp__error-msg a {
color: #CCC;
text-decoration: underline; }
......@@ -3,7 +3,7 @@ Font license info
## Font Awesome
Copyright (C) 2012 by Dave Gandy
Copyright (C) 2016 by Dave Gandy
Author: Dave Gandy
License: SIL ()
......@@ -55,3 +55,12 @@ Font license info
Homepage: http://www.justbenicestudio.com/
## Entypo
Copyright (C) 2012 by Daniel Bruce