Merge pull request #169 from electerious/v2.5

v2.5

- `New` Swipe gestures on mobile devices
- `New` Plugin-System
- `New` Rewritten Back-End
- `New` Support for ImageMagick (thanks @bb-Ricardo)
- `New` Logging-System
- `New` Blowfish hash instead of MD5 for all new passwords (thanks @bb-Ricardo)
- `New` Compile Lychee using Grunt (with npm and bower)
- `New` Open full photo without making the photo public
- `Improved` Shortcuts
- `Improved` Album share dialog
- `Improved` Database update mechanism
- `Improved` Download photos with correct title (thanks @bb-Ricardo)
- `Improved` EXIF parsing
- `Improved` URL and Server import (thanks @djdallmann)
- `Improved` Check permissions on upload
- `Fixed` Wrong capture date in Infobox
- `Fixed` Sorting by takedate
This commit is contained in:
Tobias Reich 2014-06-24 11:45:21 +02:00
commit b0d4054d13
91 changed files with 4304 additions and 2190 deletions

3
.gitignore vendored
View File

@ -8,8 +8,9 @@ uploads/import/*
uploads/big/*
uploads/thumb/*
plugins/*
etc/*
!uploads/import/index.html
!uploads/big/index.html
!uploads/thumb/index.html
!plugins/check.php
!plugins/check/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -116,7 +116,7 @@
background: -ms-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,0) 20%,rgba(0,0,0,0.9) 100%); /* IE10+ */
background: linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,0) 20%,rgba(0,0,0,0.9) 100%); /* W3C */
}
.photo .overlay {
.photo .overlay {
background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.5) 80%, rgba(0,0,0,0.9) 100%); /* FF3.6+ */
background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.5) 80%, rgba(0,0,0,0.9) 100%); /* Chrome10+,Safari5.1+ */
background: -ms-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.5) 80%, rgba(0,0,0,0.9) 100%); /* IE10+ */
@ -130,7 +130,7 @@
.album .overlay h1,
.photo .overlay h1 {
min-height: 19px;
width: 190px;
width: 185px;
margin: 153px 0px 3px 15px;
color: #fff;
font-size: 16px;
@ -156,13 +156,13 @@
margin-top: -1px;
margin-left: 12px;
padding: 12px 7px 3px 7px;
box-shadow: 0px 0px 3px #000;
box-shadow: 0px 0px 3px rgba(0, 0, 0, .8);
border-radius: 0px 0px 3px 3px;
border: 1px solid #fff;
border-top: none;
color: #fff;
font-size: 24px;
text-shadow: 0px 1px 0px #000;
text-shadow: 0px 1px 0px rgba(0, 0, 0, .4);
opacity: .9;
}
.album .badge.icon-star,
@ -236,7 +236,7 @@
color: #fff;
font-size: 14px;
font-weight: bold;
text-shadow: 0px -1px 0px #000;
text-shadow: 0px -1px 0px rgba(0, 0, 0, .8);
}
/* No Content ------------------------------------------------*/

View File

@ -15,15 +15,15 @@
top: 0px;
left: 0px;
padding: 5px 0px 6px 0px;
background-color: #393939;
background-image: -webkit-linear-gradient(top, #444, #2d2d2d);
background-image: -moz-linear-gradient(top, #393939, #2d2d2d);
background-image: -ms-linear-gradient(top, #393939, #2d2d2d);
background-image: linear-gradient(top, #393939, #2d2d2d);
border: 1px solid rgba(0,0,0,0.7);
border-bottom: 1px solid rgba(0,0,0,.9);
background-color: #444;
background-image: -webkit-linear-gradient(top, #444, #2f2f2f);
background-image: -moz-linear-gradient(top, #444, #2f2f2f);
background-image: -ms-linear-gradient(top, #444, #2f2f2f);
background-image: linear-gradient(top, #444, #2f2f2f);
border: 1px solid rgba(0,0,0,0.5);
border-bottom: 1px solid rgba(0,0,0,.7);
border-radius: 5px;
box-shadow: 0px 4px 5px rgba(0,0,0,0.3), inset 0px 1px 0px rgba(255,255,255,0.15), inset 1px 0px 0px rgba(255,255,255,0.05), inset -1px 0px 0px rgba(255,255,255,0.05);
box-shadow: 0px 3px 4px rgba(0,0,0,0.25), inset 0px 1px 0px rgba(255,255,255, .1);
opacity: 0;
z-index: 1001;
@ -36,15 +36,15 @@
.contextmenu tr {
font-size: 14px;
color: #eee;
text-shadow: 0px -1px 0px rgba(0,0,0,.6);
text-shadow: 0px -1px 0px rgba(0,0,0,.2);
cursor: pointer;
}
.contextmenu tr:hover {
background-color: #6a84f2;
background-image: -webkit-linear-gradient(top, #6a84f2, #3959ef);
background-image: -moz-linear-gradient(top, #6a84f2, #3959ef);
background-image: -ms-linear-gradient(top, #6a84f2, #3959ef);
background-image: linear-gradient(top, #6a84f2, #3959ef);
background-image: -webkit-linear-gradient(top, #6a84f2, #4967F0);
background-image: -moz-linear-gradient(top, #6a84f2, #4967F0);
background-image: -ms-linear-gradient(top, #6a84f2, #4967F0);
background-image: linear-gradient(top, #6a84f2, #4967F0);
}
.contextmenu tr.no_hover:hover {
cursor: inherit;
@ -55,8 +55,8 @@
float: left;
height: 1px;
width: 100%;
background-color: #1c1c1c;
border-bottom: 1px solid #4a4a4a;
background-color: #1f1f1f;
border-bottom: 1px solid #4c4c4c;
margin: 5px 0px;
cursor: inherit;
}
@ -74,7 +74,7 @@
.contextmenu tr:hover td {
color: #fff;
box-shadow: inset 0px 1px 0px rgba(255,255,255,.05);
text-shadow: 0px -1px 0px rgba(0,0,0,.4);
text-shadow: 0px -1px 0px rgba(0,0,0,.2);
}
.contextmenu tr.no_hover:hover td {
box-shadow: none;
@ -90,13 +90,13 @@
.contextmenu #link {
float: right;
width: 140px;
margin: 0px -17px -1px 0px;
padding: 4px 6px 5px 6px;
margin: -1px -18px -2px -1px;
padding: 5px 7px 6px 7px;
background-color: #444;
color: #fff;
border: none;
border: 1px solid #111;
box-shadow: 0px 1px 0px rgba(255,255,255,.1);
border: 1px solid rgba(0, 0, 0, .5);
box-shadow: 0px 1px 0px rgba(255,255,255,.08);
outline: none;
border-radius: 5px;
}

View File

@ -9,7 +9,7 @@
display: none;
width: 100%;
min-height: 100%;
background-color: rgba(10,10,10,.99);
background-color: rgba(10,10,10,.98);
-webkit-transition: background-color .3s;
}
@ -19,7 +19,7 @@
background-color: inherit;
}
#imageview.full {
background-color: #040404;
background-color: rgba(0,0,0,1);
}
/* ImageView ------------------------------------------------*/
@ -33,7 +33,9 @@
background-position: 50% 50%;
background-size: contain;
-webkit-transition: top .3s, bottom .3s, margin-top .3s;
-webkit-transition: top .3s, right .3s, bottom .3s, left .3s, margin-top .3s, opacity .2s, -webkit-transform .3s cubic-bezier(0.51,.92,.24,1.15);
-moz-transition: top .3s, right .3s, bottom .3s, left .3s, margin-top .3s, opacity .2s, -moz-transform .3s cubic-bezier(0.51,.92,.24,1.15);
transition: top .3s, right .3s, bottom .3s, left .3s, margin-top .3s, opacity .2s, transform .3s cubic-bezier(0.51,.92,.24,1.15);
-webkit-animation-name: zoomIn;
-webkit-animation-duration: .3s;
@ -45,12 +47,18 @@
animation-duration: .3s;
animation-timing-function: cubic-bezier(0.51,.92,.24,1.15);
}
#imageview #image.small {
top: 50%;
right: auto;
bottom: auto;
left: 50%;
}
#imageview #image.small {
top: 50%;
right: auto;
bottom: auto;
left: 50%;
}
#imageview #image.full {
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
/* Previous/Next Buttons ------------------------------------------------*/
#imageview .arrow_wrapper {

View File

@ -11,7 +11,7 @@
height: 100%;
top: 0px;
left: 0px;
background-color: rgba(0,0,0,.85);
background-color: rgba(0,0,0,.8);
}
#infobox {
z-index: 4;
@ -77,7 +77,7 @@
font-size: 16px;
font-weight: bold;
text-align: center;
text-shadow: 0px -1px 0px #000;
text-shadow: 0px -1px 0px rgba(0, 0, 0, .8);
}
#infobox .header a {
float: right;
@ -85,7 +85,7 @@
color: #fff;
font-size: 20px;
font-weight: bold;
text-shadow: 0px -1px 0px #000;
text-shadow: 0px -1px 0px rgba(0, 0, 0, .8);
opacity: .5;
cursor: pointer;
}
@ -105,7 +105,7 @@
color: #fff;
font-size: 14px;
font-weight: bold;
text-shadow: 0px -1px 0px #000;
text-shadow: 0px -1px 0px rgba(0, 0, 0, .8);
}
/* Table ------------------------------------------------*/

View File

@ -45,7 +45,7 @@
/* Content ------------------------------------------------*/
#loading h1 {
margin: 13px;
margin: 13px 13px 0px 13px;
color: #ddd;
font-size: 14px;
font-weight: bold;

View File

@ -45,6 +45,10 @@
margin: 40px 0px 0px 50px !important;
}
#imageview .arrow_wrapper {
display: none !important;
}
.message {
position: fixed !important;
width: 100% !important;
@ -57,7 +61,7 @@
-moz-animation: moveUp .3s !important;
animation: moveUp .3s !important;
}
.upload_message {
top: 50% !important;
left: 50% !important;

View File

@ -25,7 +25,7 @@
background-image: -ms-linear-gradient(top, rgb(75, 75, 75), rgb(45, 45, 45));
background-image: linear-gradient(top, rgb(75, 75, 75), rgb(45, 45, 45));
border-radius: 5px;
box-shadow: 0px 0px 5px #000, inset 0px 1px 0px rgba(255,255,255,.08), inset 1px 0px 0px rgba(255,255,255,.03), inset -1px 0px 0px rgba(255,255,255,.03);
box-shadow: 0px 0px 5px #000, inset 0px 1px 0px rgba(255,255,255,.08);
/* Animation */
-webkit-animation-name: moveUp;
@ -47,7 +47,7 @@
color: #fff;
font-size: 16px;
font-weight: bold;
text-shadow: 0px -1px 0px #222;
text-shadow: 0px -1px 0px rgba(0, 0, 0, .3);
text-align: center;
}
.message .close {
@ -57,7 +57,7 @@
padding: 12px 14px 6px 7px;
color: #aaa;
font-size: 20px;
text-shadow: 0px -1px 0px #222;
text-shadow: 0px -1px 0px rgba(0, 0, 0, .3);
cursor: pointer;
}
.message .close:hover {
@ -72,7 +72,7 @@
padding: 12px 5% 15px 5%;
color: #eee;
font-size: 14px;
text-shadow: 0px -1px 0px #222;
text-shadow: 0px -1px 0px rgba(0, 0, 0, .3);
line-height: 20px;
}
.message p b {
@ -88,20 +88,15 @@
.message .button {
float: right;
margin: 15px 15px 15px 0px;
padding: 6px 10px 8px 10px;
background-color: #4e4e4e;
background-image: -webkit-linear-gradient(top, rgb(60, 60, 60), rgb(45, 45, 45));
background-image: -moz-linear-gradient(top, rgb(60, 60, 60), rgb(45, 45, 45));
background-image: -ms-linear-gradient(top, rgb(60, 60, 60), rgb(45, 45, 45));
background-image: linear-gradient(top, rgb(60, 60, 60), rgb(45, 45, 45));
padding: 7px 10px 8px 10px;
color: #ccc;
font-size: 14px;
font-weight: bold;
text-align: center;
text-shadow: 0px -1px 0px #222;
border-radius: 5px;
border: 1px solid #191919;
box-shadow: inset 0px 1px 0px rgba(255,255,255,.1), 0px 1px 0px rgba(255,255,255,.1);
border: 1px solid rgba(0,0,0,.4);
box-shadow: inset 0px 1px 0px rgba(255,255,255,.08), 0px 1px 0px rgba(255,255,255,.05);
cursor: pointer;
}
.message .button:first-of-type {
@ -109,22 +104,22 @@
}
.message .button.active {
color: #fff;
box-shadow: inset 0px 1px 0px rgba(255,255,255,.1), 0px 1px 0px rgba(255,255,255,.1), 0px 0px 4px #005ecc;
box-shadow: inset 0px 1px 0px rgba(255,255,255,.08), 0px 1px 0px rgba(255,255,255,.1), 0px 0px 4px #005ecc;
}
.message .button:hover {
background-color: #565757;
background-image: -webkit-linear-gradient(top, rgb(80, 80, 80), rgb(57, 57, 57));
background-image: -moz-linear-gradient(top, rgb(80, 80, 80), rgb(57, 57, 57));
background-image: -ms-linear-gradient(top, rgb(80, 80, 80), rgb(57, 57, 57));
background-image: linear-gradient(top, rgb(80, 80, 80), rgb(57, 57, 57));
background-image: -webkit-linear-gradient(top, rgb(60, 60, 60), rgb(57, 57, 57));
background-image: -moz-linear-gradient(top, rgb(60, 60, 60), rgb(57, 57, 57));
background-image: -ms-linear-gradient(top, rgb(60, 60, 60), rgb(57, 57, 57));
background-image: linear-gradient(top, rgb(60, 60, 60), rgb(57, 57, 57));
}
.message .button:active,
.message .button.pressed {
background-color: #393939;
background-image: -webkit-linear-gradient(top, rgb(57, 57, 57), rgb(70, 70, 70));
background-image: -moz-linear-gradient(top, rgb(57, 57, 57), rgb(70, 70, 70));
background-image: -ms-linear-gradient(top, rgb(57, 57, 57), rgb(70, 70, 70));
background-image: linear-gradient(top, rgb(57, 57, 57), rgb(70, 70, 70));
background-image: -webkit-linear-gradient(top, rgb(57, 57, 57), rgb(60, 60, 60));
background-image: -moz-linear-gradient(top, rgb(57, 57, 57), rgb(60, 60, 60));
background-image: -ms-linear-gradient(top, rgb(57, 57, 57), rgb(60, 60, 60));
background-image: linear-gradient(top, rgb(57, 57, 57), rgb(60, 60, 60));
}
/* Sign in ------------------------------------------------*/
@ -195,4 +190,35 @@
}
.message .copylink {
margin-bottom: 20px;
}
}
/* Radio Buttons ------------------------------------------------*/
.message .choice {
float: left;
padding: 12px 5% 15px;
color: #fff;
}
.message .choice input {
float: left;
}
.message .choice h2 {
float: left;
margin: 1px 0px 0px 8px;
color: #fff;
font-size: 14px;
font-weight: 700;
text-shadow: 0 -1px 0 rgba(0, 0, 0, .3);
}
.message .choice p {
margin-top: 2px;
padding: 0px 5% 0px 25px;
color: #aaa;
font-size: 13px;
}
.message .choice p input {
width: 100%;
padding: 10px 1px 9px;
margin-top: 10px;
}

File diff suppressed because one or more lines are too long

108
assets/js/_swipe.jquery.js Normal file
View File

@ -0,0 +1,108 @@
(function($) {
var Swipe = function(el) {
var self = this;
this.el = $(el);
this.pos = { start: { x: 0, y: 0 }, end: { x: 0, y: 0 } };
this.startTime;
el.on('touchstart', function(e) { self.touchStart(e); });
el.on('touchmove', function(e) { self.touchMove(e); });
el.on('touchend', function(e) { self.swipeEnd(); });
el.on('mousedown', function(e) { self.mouseDown(e); });
};
Swipe.prototype = {
touchStart: function(e) {
var touch = e.originalEvent.touches[0];
this.swipeStart(e, touch.pageX, touch.pageY);
},
touchMove: function(e) {
var touch = e.originalEvent.touches[0];
this.swipeMove(e, touch.pageX, touch.pageY);
},
mouseDown: function(e) {
var self = this;
this.swipeStart(e, e.pageX, e.pageY);
this.el.on('mousemove', function(e) { self.mouseMove(e); });
this.el.on('mouseup', function() { self.mouseUp(); });
},
mouseMove: function(e) {
this.swipeMove(e, e.pageX, e.pageY);
},
mouseUp: function(e) {
this.swipeEnd(e);
this.el.off('mousemove');
this.el.off('mouseup');
},
swipeStart: function(e, x, y) {
this.pos.start.x = x;
this.pos.start.y = y;
this.pos.end.x = x;
this.pos.end.y = y;
this.startTime = new Date().getTime();
this.trigger('swipeStart', e);
},
swipeMove: function(e, x, y) {
this.pos.end.x = x;
this.pos.end.y = y;
this.trigger('swipeMove', e);
},
swipeEnd: function(e) {
this.trigger('swipeEnd', e);
},
trigger: function(e, originalEvent) {
var self = this;
var
event = $.Event(e),
x = self.pos.start.x - self.pos.end.x,
y = self.pos.end.y - self.pos.start.y,
radians = Math.atan2(y, x),
direction = 'up',
distance = Math.round(Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))),
angle = Math.round(radians * 180 / Math.PI),
speed = Math.round(distance / ( new Date().getTime() - self.startTime ) * 1000);
if ( angle < 0 ) {
angle = 360 - Math.abs(angle);
}
if ( ( angle <= 45 && angle >= 0 ) || ( angle <= 360 && angle >= 315 ) ) {
direction = 'left';
} else if ( angle >= 135 && angle <= 225 ) {
direction = 'right';
} else if ( angle > 45 && angle < 135 ) {
direction = 'down';
}
event.originalEvent = originalEvent;
event.swipe = { x: x, y: y, direction: direction, distance: distance, angle: angle, speed: speed };
$(self.el).trigger(event);
}
};
$.fn.swipe = function() {
var swipe = new Swipe(this);
return this;
};
})(jQuery);

View File

@ -96,7 +96,8 @@ album = {
var title,
params,
buttons;
buttons,
isNumber = function(n) { return !isNaN(parseFloat(n)) && isFinite(n) };
buttons = [
["Create Album", function() {
@ -110,10 +111,10 @@ album = {
params = "addAlbum&title=" + escape(encodeURI(title));
lychee.api(params, function(data) {
if (data!==false) {
if (data===true) data = 1; // Avoid first album to be true
lychee.goto(data);
} else lychee.error(null, params, data);
if (data===true) data = 1; // Avoid first album to be true
if (data!==false&&isNumber(data)) lychee.goto(data);
else lychee.error(null, params, data);
});
@ -206,7 +207,14 @@ album = {
buttons = [
["Set Title", function() {
newTitle = ($(".message input.text").val()==="") ? "Untitled" : $(".message input.text").val();
// Get input
newTitle = $(".message input.text").val();
// Remove html from input
newTitle = lychee.removeHTML(newTitle);
// Set to Untitled when empty
newTitle = (newTitle==="") ? "Untitled" : newTitle;
if (visible.album()) {
@ -248,14 +256,18 @@ album = {
buttons = [
["Set Description", function() {
// Get input
description = $(".message input.text").val();
// Remove html from input
description = lychee.removeHTML(description);
if (visible.album()) {
album.json.description = description;
view.album.description();
}
params = "setAlbumDescription&albumID=" + photoID + "&description=" + escape(description);
params = "setAlbumDescription&albumID=" + photoID + "&description=" + escape(encodeURI(description));
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
@ -274,14 +286,29 @@ album = {
var params;
if ($(".message input.text").length>0&&$(".message input.text").val().length>0) {
if (!visible.message()&&album.json.public==0) {
params = "setAlbumPublic&albumID=" + albumID + "&password=" + hex_md5($(".message input.text").val());
modal.show("Share Album", "This album will be shared with one of the following properties:</p><form><div class='choice'><input type='radio' value='public' name='choice' checked><h2>Public</h2><p>Visible and accessible for everyone.</p></div><div class='choice'><input type='radio' value='password' name='choice'><h2>Password protected</h2><p>Not visible to visitors and only accessible with a valid password.<input class='text' type='password' placeholder='password' value='' style='display: none;'></p></div></form><p style='display: none;'>", [["Share Album", function() { album.setPublic(album.getID(), e) }], ["Cancel", function() {}]], -160);
$(".message .choice input:radio").on("change", function() {
if ($(this).val()==="password") $(".message .choice input.text").show();
else $(".message .choice input.text").hide();
});
return true;
}
if (visible.message()&&$(".message .choice input:checked").val()==="password") {
params = "setAlbumPublic&albumID=" + albumID + "&password=" + md5($(".message input.text").val());
album.json.password = true;
} else {
params = "setAlbumPublic&albumID=" + albumID;
params = "setAlbumPublic&albumID=" + albumID + "&password=";
album.json.password = false;
}
@ -316,7 +343,7 @@ album = {
link = "http://www.facebook.com/sharer.php?u=" + encodeURI(url) + "&t=" + encodeURI(album.json.title);
break;
case 2:
link = "mailto:?subject=" + encodeURI(album.json.title) + "&body=" + encodeURI("Hi! Check this out: " + url);
link = "mailto:?subject=" + encodeURI(album.json.title) + "&body=" + encodeURI(url);
break;
default:
link = "";

View File

@ -114,7 +114,7 @@ build = {
if (visibleControls)
view += "<div id='image' style='background-image: url(" + photoJSON.url + ")'></div>";
else
view += "<div id='image' style='background-image: url(" + photoJSON.url + "); top: 0px; right: 0px; bottom: 0px; left: 0px;'></div>";
view += "<div id='image' style='background-image: url(" + photoJSON.url + ");' class='full'></div>";
}
@ -302,7 +302,7 @@ build = {
infos = [
["", "Basics"],
["Name", photoJSON.title + editTitleHTML],
["Title", photoJSON.title + editTitleHTML],
["Uploaded", photoJSON.sysdate],
["Description", photoJSON.description + editDescriptionHTML],
["", "Image"],
@ -312,7 +312,7 @@ build = {
["Tags", build.tags(photoJSON.tags, forView)]
];
if ((photoJSON.takedate+photoJSON.make+photoJSON.model+photoJSON.shutter+photoJSON.aperture+photoJSON.focal+photoJSON.iso)!="") {
if ((photoJSON.takestamp+photoJSON.make+photoJSON.model+photoJSON.shutter+photoJSON.aperture+photoJSON.focal+photoJSON.iso)!=="null") {
infos = infos.concat([
["", "Camera"],
@ -414,7 +414,7 @@ build = {
infos = [
["", "Basics"],
["Name", albumJSON.title + editTitleHTML],
["Title", albumJSON.title + editTitleHTML],
["Description", albumJSON.description + editDescriptionHTML],
["", "Album"],
["Created", albumJSON.sysdate],

View File

@ -86,8 +86,9 @@ contextMenu = {
function() { settings.setLogin() },
function() { settings.setSorting() },
function() { settings.setDropboxKey() },
function() { window.open(lychee.website, "_newtab"); },
function() { window.open("plugins/check.php", "_newtab"); },
function() { window.open(lychee.website); },
function() { window.open("plugins/check/"); },
function() { window.open("plugins/displaylog/"); },
function() { lychee.logout() }
];
@ -98,8 +99,9 @@ contextMenu = {
["separator", -1],
["<a class='icon-info-sign'></a> About Lychee", 3],
["<a class='icon-dashboard'></a> Diagnostics", 4],
["<a class='icon-list'></a> Show Log", 5],
["separator", -1],
["<a class='icon-signout'></a> Sign Out", 5]
["<a class='icon-signout'></a> Sign Out", 6]
];
contextMenu.show(items, mouse_x, mouse_y, "right");
@ -210,6 +212,26 @@ contextMenu = {
},
photoMore: function(photoID, e) {
var mouse_x = e.pageX,
mouse_y = e.pageY - $(document).scrollTop(),
items;
contextMenu.fns = [
function() { window.open(photo.getDirectLink()) },
function() { photo.getArchive(photoID) }
];
items = [
["<a class='icon-resize-full'></a> Full Photo", 0],
["<a class='icon-circle-arrow-down'></a> Download", 1]
];
contextMenu.show(items, mouse_x, mouse_y, "right");
},
move: function(photoIDs, e, orientation) {
var mouse_x = e.pageX,
@ -256,7 +278,7 @@ contextMenu = {
function() { photo.share(photoID, 1) },
function() { photo.share(photoID, 2) },
function() { photo.share(photoID, 3) },
function() { window.open(photo.getDirectLink(),"_newtab") }
function() { window.open(photo.getDirectLink()) }
];
link = photo.getViewLink(photoID);

View File

@ -20,18 +20,18 @@ $(document).ready(function(){
$(document).on("mouseup", multiselect.getSelection);
/* Header */
$("#hostedwith").on(event_name, function() { window.open(lychee.website,"_newtab") });
$("#hostedwith").on(event_name, function() { window.open(lychee.website) });
$("#button_signin").on(event_name, lychee.loginDialog);
$("#button_settings").on(event_name, contextMenu.settings);
$("#button_settings").on("click", contextMenu.settings);
$("#button_share").on(event_name, function(e) {
if (photo.json.public==1||photo.json.public==2) contextMenu.sharePhoto(photo.getID(), e);
else photo.setPublic(photo.getID(), e);
});
$("#button_share_album").on(event_name, function(e) {
if (album.json.public==1) contextMenu.shareAlbum(album.getID(), e);
else modal.show("Share Album", "All photos inside this album will be public and visible for everyone. Existing public photos will have the same sharing permission as this album. Are your sure you want to share this album? <input class='text' type='password' placeholder='password (optional)' value=''>", [["Share Album", function() { album.setPublic(album.getID(), e) }], ["Cancel", function() {}]]);
else album.setPublic(album.getID(), e);
});
$("#button_download").on(event_name, function() { photo.getArchive(photo.getID()) });
$("#button_more").on(event_name, function(e) { contextMenu.photoMore(photo.getID(), e) });
$("#button_trash_album").on(event_name, function() { album.delete([album.getID()]) });
$("#button_move").on(event_name, function(e) { contextMenu.move([photo.getID()], e) });
$("#button_trash").on(event_name, function() { photo.delete([photo.getID()]) });
@ -55,12 +55,8 @@ $(document).ready(function(){
/* Image View */
lychee.imageview
.on(event_name, ".arrow_wrapper.previous", function() {
if (album.json&&album.json.content[photo.getID()]&&album.json.content[photo.getID()].previousPhoto!=="") lychee.goto(album.getID() + "/" + album.json.content[photo.getID()].previousPhoto)
})
.on(event_name, ".arrow_wrapper.next", function() {
if (album.json&&album.json.content[photo.getID()]&&album.json.content[photo.getID()].nextPhoto!=="") lychee.goto(album.getID() + "/" + album.json.content[photo.getID()].nextPhoto)
});
.on(event_name, ".arrow_wrapper.previous", photo.previous)
.on(event_name, ".arrow_wrapper.next", photo.next);
/* Infobox */
$("#infobox")
@ -74,17 +70,40 @@ $(document).ready(function(){
/* Keyboard */
Mousetrap
.bind('u', function() { $("#upload_files").click() })
.bind('s', function() { if (visible.photo()) $("#button_star").click() })
.bind('left', function() { if (visible.photo()) $("#imageview a#previous").click() })
.bind('right', function() { if (visible.photo()) $("#imageview a#next").click() })
.bind('command+backspace', function() {
.bind(['u', 'ctrl+u'], function() { $("#upload_files").click() })
.bind(['s', 'ctrl+s', 'f', 'ctrl+f'], function(e) {
if (visible.photo()) {
$("#button_star").click();
} else if (visible.albums()) {
e.preventDefault();
$("#search").focus();
}
})
.bind(['r', 'ctrl+r'], function(e) {
e.preventDefault();
if (visible.album()) album.setTitle(album.getID());
else if (visible.photo()) photo.setTitle([photo.getID()]);
})
.bind(['d', 'ctrl+d'], function(e) {
e.preventDefault();
if (visible.photo()) photo.setDescription(photo.getID());
else if (visible.album()) album.setDescription(album.getID());
})
.bind(['t', 'ctrl+t'], function(e) {
if (visible.photo()) {
e.preventDefault();
photo.editTags([photo.getID()]);
}
})
.bind(['i', 'ctrl+i'], function() {
if (visible.infobox()) view.infobox.hide();
else if (visible.infoboxbutton()) view.infobox.show();
})
.bind(['command+backspace', 'ctrl+backspace'], function() {
if (visible.photo()&&!visible.message()) photo.delete([photo.getID()]);
else if (visible.album()&&!visible.message()) album.delete([album.getID()]);
})
.bind('i', function() {
if (visible.infobox()) view.infobox.hide();
else if (!visible.albums()) view.infobox.show();
});
Mousetrap.bindGlobal('enter', function() {
@ -101,6 +120,26 @@ $(document).ready(function(){
else if (visible.albums()&&$("#search").val().length!==0) search.reset();
});
if (mobileBrowser()) {
$(document)
/* Fullscreen on mobile */
.on('touchend', '#image', function(e) {
if (swipe.obj===null||(swipe.offset>=-5&&swipe.offset<=5)) {
if (visible.controls()) view.header.hide(e, 0);
else view.header.show();
}
})
/* Swipe on mobile */
.swipe().on('swipeStart', function() { if (visible.photo()) swipe.start($("#image")) })
.swipe().on('swipeMove', function(e) { if (visible.photo()) swipe.move(e.swipe) })
.swipe().on('swipeEnd', function(e) { if (visible.photo()) swipe.stop(e.swipe, photo.previous, photo.next) });
}
/* Document */
$(document)

View File

@ -11,48 +11,77 @@ loadingBar = {
show: function(status, errorText) {
if (status==="error") {
if (status==='error') {
loadingBar.status = "error";
// Set status
loadingBar.status = 'error';
if (!errorText) errorText = "Whoops, it looks like something went wrong. Please reload the site and try again!";
// Parse text
if (errorText) errorText = errorText.replace('<br>', '');
if (!errorText) errorText = 'Whoops, it looks like something went wrong. Please reload the site and try again!';
// Move header down
if (visible.controls()) lychee.header.addClass('error');
// Modify loading
lychee.loadingBar
.removeClass("loading uploading error")
.removeClass('loading uploading error')
.addClass(status)
.html("<h1>Error: <span>" + errorText + "</span></h1>")
.html('<h1>Error: <span>' + errorText + '</span></h1>')
.show()
.css("height", "40px");
if (visible.controls()) lychee.header.addClass("error");
.css('height', '40px');
clearTimeout(lychee.loadingBar.data("timeout"));
lychee.loadingBar.data("timeout", setTimeout(function() { loadingBar.hide(true) }, 3000));
// Set timeout
clearTimeout(lychee.loadingBar.data('timeout'));
lychee.loadingBar.data('timeout', setTimeout(function() { loadingBar.hide(true) }, 3000));
} else if (loadingBar.status===null) {
return true;
loadingBar.status = "loading";
}
clearTimeout(lychee.loadingBar.data("timeout"));
lychee.loadingBar.data("timeout", setTimeout(function() {
if (loadingBar.status===null) {
// Set status
loadingBar.status = 'loading';
// Set timeout
clearTimeout(lychee.loadingBar.data('timeout'));
lychee.loadingBar.data('timeout', setTimeout(function() {
// Move header down
if (visible.controls()) lychee.header.addClass('loading');
// Modify loading
lychee.loadingBar
.show()
.removeClass("loading uploading error")
.addClass("loading");
if (visible.controls()) lychee.header.addClass("loading");
.removeClass('loading uploading error')
.addClass('loading')
.show();
}, 1000));
return true;
}
},
hide: function(force_hide) {
hide: function(force) {
if ((loadingBar.status!=="error"&&loadingBar.status!==null)||force_hide) {
if ((loadingBar.status!=='error'&&loadingBar.status!==null)||force) {
// Remove status
loadingBar.status = null;
clearTimeout(lychee.loadingBar.data("timeout"));
lychee.loadingBar.html("").css("height", "3px");
if (visible.controls()) lychee.header.removeClass("error loading");
// Move header up
if (visible.controls()) lychee.header.removeClass('error loading');
// Modify loading
lychee.loadingBar
.html('')
.css('height', '3px');
// Set timeout
clearTimeout(lychee.loadingBar.data('timeout'));
setTimeout(function() { lychee.loadingBar.hide() }, 300);
}

View File

@ -7,7 +7,9 @@
var lychee = {
version: "2.1.1",
title: "",
version: "2.5 rc1",
version_code: "020500",
api_path: "php/api.php",
update_path: "http://lychee.electerious.com/version/index.php",
@ -38,7 +40,7 @@ var lychee = {
var params;
params = "init&version=" + escape(lychee.version);
params = "init&version=" + lychee.version_code;
lychee.api(params, function(data) {
if (data.loggedIn!==true) {
@ -86,6 +88,7 @@ var lychee = {
if (typeof data==="string"&&data.substring(0, 7)==="Error: ") {
lychee.error(data.substring(7, data.length), params, data);
upload.close(true);
return false;
}
@ -112,13 +115,13 @@ var lychee = {
login: function() {
var user = $("input#username").val(),
password = hex_md5($("input#password").val()),
password = md5($("input#password").val()),
params;
params = "login&user=" + user + "&password=" + password;
lychee.api(params, function(data) {
if (data===true) {
localStorage.setItem("username", user);
localStorage.setItem("lychee_username", user);
window.location.reload();
} else {
$("#password").val("").addClass("error").focus();
@ -135,7 +138,7 @@ var lychee = {
$("body").append(build.signInModal());
$("#username").focus();
if (localStorage) {
local_username = localStorage.getItem("username");
local_username = localStorage.getItem("lychee_username");
if (local_username!==null) {
if (local_username.length>0) $("#username").val(local_username);
$("#password").focus();
@ -166,6 +169,7 @@ var lychee = {
photoID = "",
hash = document.location.hash.replace("#", "").split("/");
$(".no_content").remove();
contextMenu.close();
multiselect.close();
@ -224,8 +228,10 @@ var lychee = {
setTitle: function(title, editable) {
if (title==="Albums") document.title = "Lychee";
else document.title = "Lychee - " + title;
if (lychee.title==="") lychee.title = document.title;
if (title==="Albums") document.title = lychee.title;
else document.title = lychee.title + " - " + title;
if (editable) $("#title").addClass("editable");
else $("#title").removeClass("editable");
@ -248,10 +254,12 @@ var lychee = {
.off("drop");
Mousetrap
.unbind('n')
.unbind('u')
.unbind('s')
.unbind('backspace');
.unbind(['u', 'ctrl+u'])
.unbind(['s', 'ctrl+s'])
.unbind(['r', 'ctrl+r'])
.unbind(['d', 'ctrl+d'])
.unbind(['t', 'ctrl+t'])
.unbind(['command+backspace', 'ctrl+backspace']);
if (mode==="public") {
@ -328,6 +336,14 @@ var lychee = {
},
removeHTML: function(html) {
var tmp = document.createElement("DIV");
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText;
},
error: function(errorThrown, params, data) {
console.log("Error Description: " + errorThrown);

View File

@ -22,7 +22,7 @@ password = {
view.album.password();
}
params = "setAlbumPassword&albumID=" + albumID + "&password=" + hex_md5($(".message input.text").val());
params = "setAlbumPassword&albumID=" + albumID + "&password=" + md5($(".message input.text").val());
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
@ -58,11 +58,11 @@ password = {
} else {
// Check password
params = "checkAlbumAccess&albumID=" + albumID + "&password=" + hex_md5(passwd);
params = "checkAlbumAccess&albumID=" + albumID + "&password=" + md5(passwd);
lychee.api(params, function(data) {
if (data===true) {
password.value = hex_md5(passwd);
password.value = md5(passwd);
callback();
} else {
lychee.goto("");

View File

@ -56,6 +56,68 @@ photo = {
},
previous: function(animate) {
var delay = 0;
if (photo.getID()!==false&&
album.json&&
album.json.content[photo.getID()]&&
album.json.content[photo.getID()].previousPhoto!=="") {
if (animate===true) {
delay = 200;
$("#image").css({
WebkitTransform: 'translateX(100%)',
MozTransform: 'translateX(100%)',
transform: 'translateX(100%)',
opacity: 0
});
}
setTimeout(function() {
if (photo.getID()===false) return false;
lychee.goto(album.getID() + "/" + album.json.content[photo.getID()].previousPhoto)
}, delay);
}
},
next: function(animate) {
var delay = 0;
if (photo.getID()!==false&&
album.json&&
album.json.content[photo.getID()]&&
album.json.content[photo.getID()].nextPhoto!=="") {
if (animate===true) {
delay = 200;
$("#image").css({
WebkitTransform: 'translateX(-100%)',
MozTransform: 'translateX(-100%)',
transform: 'translateX(-100%)',
opacity: 0
});
}
setTimeout(function() {
if (photo.getID()===false) return false;
lychee.goto(album.getID() + "/" + album.json.content[photo.getID()].nextPhoto);
}, delay);
}
},
delete: function(photoIDs) {
var params,
@ -145,8 +207,12 @@ photo = {
buttons = [
["Set Title", function() {
// Get input
newTitle = $(".message input.text").val();
// Remove html from input
newTitle = lychee.removeHTML(newTitle);
if (visible.photo()) {
photo.json.title = (newTitle==="") ? "Untitled" : newTitle;
view.photo.title();
@ -256,7 +322,7 @@ photo = {
album.json.content[photoID].public = (album.json.content[photoID].public==0) ? 1 : 0;
view.album.content.public(photoID);
params = "setPhotoPublic&photoID=" + photoID + "&url=" + photo.getViewLink(photoID);
params = "setPhotoPublic&photoID=" + photoID;
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
@ -275,14 +341,18 @@ photo = {
buttons = [
["Set Description", function() {
// Get input
description = $(".message input.text").val();
// Remove html from input
description = lychee.removeHTML(description);
if (visible.photo()) {
photo.json.description = description;
view.photo.description();
}
params = "setPhotoDescription&photoID=" + photoID + "&description=" + escape(description);
params = "setPhotoDescription&photoID=" + photoID + "&description=" + escape(encodeURI(description));
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
@ -300,7 +370,8 @@ photo = {
editTags: function(photoIDs) {
var oldTags = "",
tags = "";
tags = "",
buttons;
if (!photoIDs) return false;
if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
@ -345,7 +416,10 @@ photo = {
// Parse tags
tags = tags.replace(/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/g, ',');
tags = tags.replace(/,$|^,/g, '');
tags = tags.replace(/,$|^,|(\ ){0,}$/g, '');
// Remove html from input
tags = lychee.removeHTML(tags);
if (visible.photo()) {
photo.json.tags = tags;

View File

@ -45,14 +45,14 @@ search = {
else if (photosData==="") code = build.divider("Albums")+albumsData;
else code = build.divider("Photos")+photosData+build.divider("Albums")+albumsData;
if (search.code!==hex_md5(code)) {
if (search.code!==md5(code)) {
$(".no_content").remove();
lychee.animate(".album, .photo", "contentZoomOut");
lychee.animate(".divider", "fadeOut");
search.code = hex_md5(code);
search.code = md5(code);
setTimeout(function() {

View File

@ -13,7 +13,8 @@ var settings = {
dbUser,
dbPassword,
dbHost,
buttons;
buttons,
params;
buttons = [
["Connect", function() {
@ -26,7 +27,7 @@ var settings = {
if (dbHost.length<1) dbHost = "localhost";
if (dbName.length<1) dbName = "lychee";
params = "dbCreateConfig&dbName=" + escape(dbName) + "&dbUser=" + escape(dbUser) + "&dbPassword=" + escape(dbPassword) + "&dbHost=" + escape(dbHost) + "&version=" + escape(lychee.version);
params = "dbCreateConfig&dbName=" + escape(dbName) + "&dbUser=" + escape(dbUser) + "&dbPassword=" + escape(dbPassword) + "&dbHost=" + escape(dbHost);
lychee.api(params, function(data) {
if (data!==true) {
@ -113,7 +114,7 @@ var settings = {
} else {
params = "setLogin&username=" + escape(username) + "&password=" + hex_md5(password);
params = "setLogin&username=" + escape(username) + "&password=" + md5(password);
lychee.api(params, function(data) {
if (data!==true) {
@ -173,7 +174,7 @@ var settings = {
return false;
}
params = "setLogin&oldPassword=" + hex_md5(old_password) + "&username=" + escape(username) + "&password=" + hex_md5(password);
params = "setLogin&oldPassword=" + md5(old_password) + "&username=" + escape(username) + "&password=" + md5(password);
lychee.api(params, function(data) {
if (data!==true) lychee.error(null, params, data);
@ -191,7 +192,8 @@ var settings = {
setSorting: function() {
var buttons,
sorting;
sorting,
params;
buttons = [
["Change Sorting", function() {
@ -216,7 +218,7 @@ var settings = {
"Sort photos by \
<select id='settings_type'> \
<option value='id'>Upload Time</option> \
<option value='take'>Take Date</option> \
<option value='takestamp'>Take Date</option> \
<option value='title'>Title</option> \
<option value='description'>Description</option> \
<option value='public'>Public</option> \
@ -232,13 +234,12 @@ var settings = {
", buttons);
if (lychee.sorting!=="") {
sorting = lychee.sorting.replace("ORDER BY ", "").split(" ");
// Special parsing
if (sorting[0]==='UNIX_TIMESTAMP(STR_TO_DATE(CONCAT(takedate,"-",taketime),"%d.%m.%Y-%H:%i:%S"))') sorting[0] = "take";
sorting = lychee.sorting.replace("ORDER BY ", "").split(" ");
$("select#settings_type").val(sorting[0]);
$("select#settings_order").val(sorting[1]);
}
},

54
assets/js/swipe.js Normal file
View File

@ -0,0 +1,54 @@
/**
* @name Swipe Module
* @description Swipes and moves an object.
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
swipe = {
obj: null,
tolerance: 150,
offset: 0,
start: function(obj, tolerance) {
if (obj) swipe.obj = obj;
if (tolerance) swipe.tolerance = tolerance;
return true;
},
move: function(e) {
if (swipe.obj===null) return false;
swipe.offset = -1 * e.x;
swipe.obj.css({
WebkitTransform: 'translateX(' + swipe.offset + 'px)',
MozTransform: 'translateX(' + swipe.offset + 'px)',
transform: 'translateX(' + swipe.offset + 'px)'
});
},
stop: function(e, left, right) {
if (e.x<=-swipe.tolerance) left(true);
else if (e.x>=swipe.tolerance) right(true);
else if (swipe.obj!==null) {
swipe.obj.css({
WebkitTransform: 'translateX(0px)',
MozTransform: 'translateX(0px)',
transform: 'translateX(0px)'
});
}
swipe.obj = null;
swipe.offset = 0;
}
};

View File

@ -63,7 +63,7 @@ upload = {
progress;
if (files.length<=0) return false;
if (albumID===false) albumID = 0;
if (albumID===false||visible.albums()===true) albumID = 0;
formData.append("function", "upload");
formData.append("albumID", albumID);
@ -132,7 +132,8 @@ upload = {
var albumID = album.getID(),
params,
extension,
buttons;
buttons,
link;
if (albumID===false) albumID = 0;

View File

@ -11,49 +11,40 @@ view = {
show: function() {
var newMargin = -1*($("#imageview #image").height()/2)+20;
clearTimeout($(window).data("timeout"));
if (visible.photo()) {
lychee.imageview.removeClass("full");
lychee.loadingBar.css("opacity", 1);
lychee.header.removeClass("hidden");
if ($("#imageview #image.small").length>0) {
$("#imageview #image").css({
marginTop: -1*($("#imageview #image").height()/2)+20
});
} else {
$("#imageview #image").css({
top: 60,
right: 30,
bottom: 30,
left: 30
});
}
}
lychee.imageview.removeClass("full");
lychee.header.removeClass("hidden");
lychee.loadingBar.css("opacity", 1);
if ($("#imageview #image.small").length>0) $("#imageview #image").css('margin-top', newMargin);
else $("#imageview #image").removeClass('full');
},
hide: function() {
hide: function(e, delay) {
var newMargin = -1*($("#imageview #image").height()/2);
if (delay===undefined) delay = 500;
if (visible.photo()&&!visible.infobox()&&!visible.contextMenu()&&!visible.message()) {
clearTimeout($(window).data("timeout"));
$(window).data("timeout", setTimeout(function() {
lychee.imageview.addClass("full");
lychee.loadingBar.css("opacity", 0);
lychee.header.addClass("hidden");
if ($("#imageview #image.small").length>0) {
$("#imageview #image").css({
marginTop: -1*($("#imageview #image").height()/2)
});
} else {
$("#imageview #image").css({
top: 0,
right: 0,
bottom: 0,
left: 0
});
}
}, 500));
lychee.loadingBar.css("opacity", 0);
if ($("#imageview #image.small").length>0) $("#imageview #image").css('margin-top', newMargin);
else $("#imageview #image").addClass('full');
}, delay));
}
},
@ -229,7 +220,7 @@ view = {
lychee.setTitle("Unsorted", false);
break;
default:
if (album.json.init) $("#infobox .attr_name").html(album.json.title + " " + build.editIcon("edit_title_album"));
if (album.json.init) $("#infobox .attr_title").html(album.json.title + " " + build.editIcon("edit_title_album"));
lychee.setTitle(album.json.title, true);
break;
}
@ -381,7 +372,7 @@ view = {
hide: function() {
if (!visible.controls()) view.header.show();
view.header.show();
if (visible.infobox) view.infobox.hide();
lychee.content.removeClass("view");
@ -406,7 +397,7 @@ view = {
title: function() {
if (photo.json.init) $("#infobox .attr_name").html(photo.json.title + " " + build.editIcon("edit_title"));
if (photo.json.init) $("#infobox .attr_title").html(photo.json.title + " " + build.editIcon("edit_title"));
lychee.setTitle(photo.json.title, true);
},

View File

@ -32,6 +32,13 @@ visible = {
else return false;
},
infoboxbutton: function() {
if (visible.albums()) return false;
if (visible.photo()) return true;
if (visible.album()&&$('#button_info_album:visible').length>0) return true;
else return false;
},
controls: function() {
if (lychee.loadingBar.css('opacity')<1) return false;
else return true;

2
assets/min/main.css Normal file

File diff suppressed because one or more lines are too long

7
assets/min/main.js Normal file

File diff suppressed because one or more lines are too long

5
assets/min/view.js Normal file

File diff suppressed because one or more lines are too long

108
build/Gruntfile.coffee Normal file
View File

@ -0,0 +1,108 @@
module.exports = (grunt) ->
grunt.initConfig
pkg: grunt.file.readJSON 'package.json'
concat:
view:
options:
separator: "\n"
src: [
'bower_components/jQuery/dist/jquery.min.js'
'bower_components/js-md5/js/md5.min.js'
'bower_components/mousetrap/mousetrap.min.js'
'bower_components/mousetrap/plugins/global-bind/mousetrap-global-bind.min.js'
'../assets/js/_frameworks.js'
'../assets/js/build.js'
'../assets/js/view/main.js'
]
dest: '../assets/min/view.js'
js:
options:
separator: "\n"
src: [
'bower_components/jQuery/dist/jquery.min.js'
'bower_components/js-md5/js/md5.min.js'
'bower_components/mousetrap/mousetrap.min.js'
'bower_components/mousetrap/plugins/global-bind/mousetrap-global-bind.min.js'
'../assets/js/*.js'
]
dest: '../assets/min/main.js'
css:
options:
separator: "\n"
src: [
'../assets/css/*.css'
]
dest: '../assets/min/main.css'
uglify:
view:
options:
banner: '/*! <%= pkg.name %> <%= pkg.version %> */\n'
files:
'../assets/min/view.js': '../assets/min/view.js'
assets:
options:
banner: '/*! <%= pkg.name %> <%= pkg.version %> */\n'
files:
'../assets/min/main.js': '../assets/min/main.js'
cssmin:
assets:
options:
banner: '/*! <%= pkg.name %> <%= pkg.version %> */'
files:
'../assets/min/main.css': '../assets/min/main.css'
watch:
js:
files: [
'../assets/js/*.js'
]
tasks: ['js']
options:
spawn: false
interrupt: true
css:
files: [
'../assets/css/*.css'
]
tasks: ['css']
options:
spawn: false
interrupt: true
require('load-grunt-tasks')(grunt)
grunt.registerTask 'default', ->
grunt.task.run [
'view'
'js'
'css'
]
grunt.registerTask 'view', [
'concat:view'
'uglify:view'
]
grunt.registerTask 'js', [
'concat:js'
'uglify:assets'
]
grunt.registerTask 'css', [
'concat:css'
'cssmin:assets'
]

8
build/bower.json Normal file
View File

@ -0,0 +1,8 @@
{
"name": "Lychee",
"dependencies": {
"jQuery": "~2.1.0",
"js-md5": "~1.1.0",
"mousetrap": "~1.4.6"
}
}

22
build/package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "Lychee",
"version": "2.5.0",
"description": "Self-hosted photo-management done right.",
"authors": "Tobias Reich <tobias.reich.ich@gmail.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/electerious/Lychee.git"
},
"devDependencies": {
"grunt": "~0.4.2",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-coffee": "~0.10.1",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-cssmin": "~0.9.0",
"grunt-contrib-uglify": "~0.4.0",
"grunt-contrib-watch": "~0.6.0",
"grunt-notify": "^0.3.0",
"load-grunt-tasks": "~0.4.0"
}
}

View File

@ -2,19 +2,25 @@
First you have to install the following dependencies:
- [CSS Optimizer](https://github.com/css/csso) `csso`
- [UglifyJS](https://github.com/mishoo/UglifyJS2) `uglifyjs`
- `node` [Node.js](http://nodejs.org) v0.10 or later
- `npm` [Node Packaged Modules](https://www.npmjs.org)
- `bower` [Bower](http://bower.io)
- `grunt` [Grunt](http://gruntjs.com)
These dependencies can be installed using `npm`:
After [installing Node.js](http://nodejs.org) you can use the included `npm` package manager to install the global requirements and Lychee-dependencies with the following command:
npm install csso uglify-js -g;
npm install -g bower grunt-cli;
npm install && bower install;
### Build
The Makefile is located in `etc/` and can be easily executed, using the following command. Make sure your run this from the root of Lychee:
make -f etc/Makefile
The Gruntfile is located in `build/` and can be easily executed using the `grunt` command.
### Use uncompressed files
### Watch for changes
While developing, you might want to use the uncompressed files. This is possible by editing the `index.html`. Simply change the linked CSS and JS files. There are already out-commented link-tags for development and production.
While developing, you might want to use the following command to watch for changes in `assets/js/` and `assets/css/`:
grunt watch
`grunt` will automatically build Lychee, everytime you save a file.

View File

@ -1,3 +1,25 @@
## v2.5
Released -
- `New` Swipe gestures on mobile devices
- `New` Plugin-System
- `New` Rewritten Back-End
- `New` Support for ImageMagick (thanks @bb-Ricardo)
- `New` Logging-System
- `New` Blowfish hash instead of MD5 for all new passwords (thanks @bb-Ricardo)
- `New` Compile Lychee using Grunt (with npm and bower)
- `New` Open full photo without making the photo public
- `Improved` Shortcuts
- `Improved` Album share dialog
- `Improved` Database update mechanism
- `Improved` Download photos with correct title (thanks @bb-Ricardo)
- `Improved` EXIF parsing
- `Improved` URL and Server import (thanks @djdallmann)
- `Improved` Check permissions on upload
- `Fixed` Wrong capture date in Infobox
- `Fixed` Sorting by takedate
## v2.1.1
Released March 20, 2014

View File

@ -1,5 +1,5 @@
#### Lychee is not working
If Lychee is not working properly, try to open `plugins/check.php`. This script will display all errors it can find.
If Lychee is not working properly, try to open `plugins/check/index.php`. This script will display all errors it can find.
#### What do I need to run Lychee on my server?
To run Lychee, everything you need is a web-server with PHP 5.3 or later and a MySQL-Database.
@ -13,6 +13,7 @@ If possible, change these settings directly in your `php.ini`. We recommend to i
upload_max_size = 200M
upload_max_filesize = 20M
max_file_uploads = 100
memory_limit = 256M
#### Which browsers are supported?
Lychee supports the latest versions of Google Chrome, Apple Safari, Mozilla Firefox and Opera. Photos you share with others can be viewed from every browser.
@ -34,6 +35,12 @@ To backup your Lychee installation you need to do the following steps:
- INSERT INTO lychee_photos_backup SELECT * FROM lychee_photos;
- CREATE TABLE lychee_settings_backup LIKE lychee_settings;
- INSERT INTO lychee_settings_backup SELECT * FROM lychee_settings;
#### Can I use my existing folder-structure?
No. Lychee has it's own folder-structure and database. Please upload or import all your photos to use them.
#### Can I upload videos?
No. Video support is not planned.
#### What's the advantage of buying Lychee?
Lychee is completely free to use for personal usage. However, if you like Lychee or want to use in commercially, you need to buy Lychee from [our site](http://lychee.electerious.com). I hope you appreciate my work and support further development by buying a license.

View File

@ -12,6 +12,7 @@ To use Lychee without restrictions, we recommend to increase the values of the f
upload_max_size = 200M
upload_max_filesize = 20M
max_file_uploads = 100
memory_limit = 256M
You might also take a look at [Issue #106](https://github.com/electerious/Lychee/issues/106) if you are using nginx or in the [FAQ](https://github.com/electerious/Lychee/blob/master/docs/FAQ.md#i-cant-upload-multiple-photos-at-once) if you are using CGI or FastCGI.

View File

@ -1,22 +1,37 @@
### About
The following keys and shortcuts can be used in Lychee. Single char-shortcuts are also working, when using them with together the `ctrl`-key.
### Everywhere
| Key | Action |
|:-----------|:------------|
| `enter` | Confirm Dialog |
| `u` | Upload photo |
| `esc` | Close/Back |
| `cmd`+`up` | Close/Back |
| `enter` | Confirm Dialog |
| `esc` or `cmd`+`up` | Close/Back |
### Albums
| Key | Action |
|:-----------|:------------|
| `s` or `f` | Search |
### Album
| Key | Action |
|:-----------|:------------|
| `r` | Set title |
| `d` | Set description |
| `i` | Show information |
| `cmd`+`backspace` | Delete album |
| `ctrl`+`backspace` | Delete album |
### Photo
| Key | Action |
|:-----------|:------------|
| `s` | Star photo |
| `i` | Show information |
| `cmd`+`backspace` | Delete photo |
| `r` | Set title |
| `t` | Set tags |
| `d` | Set description |
| `s` or `f` | Star photo |
| `i` | Show information |
| `left` | Previous photo |
| `right` | Next photo |
| `cmd`+`backspace` | Delete photo |
| `ctrl`+`backspace` | Delete photo |

View File

@ -1,35 +0,0 @@
NO_COLOR=\x1b[0m
OK_COLOR=\x1b[32;01m
ERROR_COLOR=\x1b[31;01m
WARN_COLOR=\x1b[33;01m
OK_STRING=$(OK_COLOR)[OK]$(NO_COLOR)
ERROR_STRING=$(ERROR_COLOR)[ERRORS]$(NO_COLOR)
RUN_STRING=$(WARN_COLOR)[RUN]$(NO_COLOR)
ROOT = .
CSS = '$(ROOT)/assets/css'
JS = '$(ROOT)/assets/js'
BUILD = '$(ROOT)/assets/build'
all: clean create css js
clean:
rm -f -R $(BUILD)
@echo "$(OK_STRING) Clean build"
create: clean
mkdir $(BUILD)
@echo "$(OK_STRING) Create build"
css: create
@echo "$(RUN_STRING) Compiling CSS"
awk 'FNR==1{print ""}1' $(CSS)/*.css > $(BUILD)/main.css
csso $(BUILD)/main.css $(BUILD)/main.css
@echo "$(OK_STRING) CSS compiled"
js: create
@echo "$(RUN_STRING) Compiling JS"
awk 'FNR==1{print ""}1' $(JS)/*.js > $(BUILD)/main.js
uglifyjs $(BUILD)/main.js -o $(BUILD)/main.js
@echo "$(OK_STRING) JS compiled"

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

View File

@ -1 +0,0 @@
Entypo

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>app</key>
<string>com.bohemiancoding.sketch</string>
<key>build</key>
<integer>5302</integer>
<key>commit</key>
<string>9460a4bc62af5e9ba50dd4143578fd9401710ce5</string>
<key>version</key>
<integer>18</integer>
</dict>
</plist>

View File

@ -1 +0,0 @@
18

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>app</key>
<string>com.bohemiancoding.sketch</string>
<key>build</key>
<integer>5302</integer>
<key>commit</key>
<string>9460a4bc62af5e9ba50dd4143578fd9401710ce5</string>
<key>version</key>
<integer>18</integer>
</dict>
</plist>

View File

@ -1 +0,0 @@
18

View File

@ -9,25 +9,8 @@
<meta name="keywords" content="">
<meta name="description" content="">
<!-- Development
<link type="text/css" rel="stylesheet" href="assets/css/_reset.css">
<link type="text/css" rel="stylesheet" href="assets/css/upload.css">
<link type="text/css" rel="stylesheet" href="assets/css/tooltip.css">
<link type="text/css" rel="stylesheet" href="assets/css/misc.css">
<link type="text/css" rel="stylesheet" href="assets/css/message.css">
<link type="text/css" rel="stylesheet" href="assets/css/mediaquery.css">
<link type="text/css" rel="stylesheet" href="assets/css/loading.css">
<link type="text/css" rel="stylesheet" href="assets/css/infobox.css">
<link type="text/css" rel="stylesheet" href="assets/css/imageview.css">
<link type="text/css" rel="stylesheet" href="assets/css/header.css">
<link type="text/css" rel="stylesheet" href="assets/css/font.css">
<link type="text/css" rel="stylesheet" href="assets/css/contextmenu.css">
<link type="text/css" rel="stylesheet" href="assets/css/content.css">
<link type="text/css" rel="stylesheet" href="assets/css/animations.css">
<link type="text/css" rel="stylesheet" href="assets/css/multiselect.css"> -->
<!-- Production -->
<link type="text/css" rel="stylesheet" href="assets/build/main.css">
<!-- CSS -->
<link type="text/css" rel="stylesheet" href="assets/min/main.css">
<link rel="shortcut icon" href="favicon.ico">
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-iphone.png" sizes="120x120">
@ -61,16 +44,16 @@
<a class="button right icon icon-plus button_add"></a>
<a class="button_divider"></a>
<div class="tools" id="button_trash_album" title="Delete Album"><a class="icon-trash"></a></div>
<div class="tools" id="button_archive" title="Download Album"><a class="icon-circle-arrow-down"></a></div>
<div class="tools" id="button_info_album" title="Show Info"><a class="icon-info-sign"></a></div>
<div class="tools" id="button_archive" title="Download Album"><a class="icon-circle-arrow-down"></a></div>
<div class="tools" id="button_share_album" title="Share Album"><a class="icon-share"></a></div>
</div>
<div id="tools_photo">
<a class="button left icon-arrow-left" id="button_back"></a>
<div class="tools" id="button_more" title="More"><a class="icon-caret-down"></a></div>
<a class="button_divider"></a>
<div class="tools" id="button_trash" title="Delete"><a class="icon-trash"></a></div>
<div class="tools" id="button_move" title="Move"><a class="icon-folder-open"></a></div>
<a class="button_divider"></a>
<div class="tools" id="button_download" title="Download"><a class="icon-circle-arrow-down"></a></div>
<div class="tools" id="button_info" title="Show Info"><a class="icon-info-sign"></a></div>
<a class="button_divider"></a>
<div class="tools" id="button_share" title="Share Photo"><a class="icon-share"></a></div>
@ -95,27 +78,8 @@
<input id="upload_files" type="file" name="fileElem[]" multiple accept="image/*">
</div>
<!-- Development
<script defer type="text/javascript" src="assets/js/_frameworks.js"></script>
<script defer type="text/javascript" src="assets/js/init.js"></script>
<script defer type="text/javascript" src="assets/js/lychee.js"></script>
<script defer type="text/javascript" src="assets/js/build.js"></script>
<script defer type="text/javascript" src="assets/js/settings.js"></script>
<script defer type="text/javascript" src="assets/js/upload.js"></script>
<script defer type="text/javascript" src="assets/js/view.js"></script>
<script defer type="text/javascript" src="assets/js/password.js"></script>
<script defer type="text/javascript" src="assets/js/modal.js"></script>
<script defer type="text/javascript" src="assets/js/album.js"></script>
<script defer type="text/javascript" src="assets/js/albums.js"></script>
<script defer type="text/javascript" src="assets/js/photo.js"></script>
<script defer type="text/javascript" src="assets/js/visible.js"></script>
<script defer type="text/javascript" src="assets/js/loadingBar.js"></script>
<script defer type="text/javascript" src="assets/js/contextMenu.js"></script>
<script defer type="text/javascript" src="assets/js/search.js"></script>
<script defer type="text/javascript" src="assets/js/multiselect.js"></script> -->
<!-- Production -->
<script async type="text/javascript" src="assets/build/main.js"></script>
<!-- JS -->
<script async type="text/javascript" src="assets/min/main.js"></script>
</body>
</html>

30
php/access/Access.php Normal file
View File

@ -0,0 +1,30 @@
<?php
###
# @name Access
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Access {
protected $database = null;
protected $plugins = null;
protected $settings = null;
public function __construct($database, $plugins, $settings) {
# Init vars
$this->database = $database;
$this->plugins = $plugins;
$this->settings = $settings;
return true;
}
}
?>

306
php/access/Admin.php Normal file
View File

@ -0,0 +1,306 @@
<?php
###
# @name Admin Access
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
if (!defined('LYCHEE_ACCESS_ADMIN')) exit('Error: You are not allowed to access this area!');
class Admin extends Access {
public function check($fn) {
switch ($fn) {
# Album functions
case 'getAlbums': $this->getAlbums(); break;
case 'getAlbum': $this->getAlbum(); break;
case 'addAlbum': $this->addAlbum(); break;
case 'setAlbumTitle': $this->setAlbumTitle(); break;
case 'setAlbumDescription': $this->setAlbumDescription(); break;
case 'setAlbumPublic': $this->setAlbumPublic(); break;
case 'setAlbumPassword': $this->setAlbumPassword(); break;
case 'deleteAlbum': $this->deleteAlbum(); break;
# Photo functions
case 'getPhoto': $this->getPhoto(); break;
case 'setPhotoTitle': $this->setPhotoTitle(); break;
case 'setPhotoDescription': $this->setPhotoDescription(); break;
case 'setPhotoStar': $this->setPhotoStar(); break;
case 'setPhotoPublic': $this->setPhotoPublic(); break;
case 'setPhotoAlbum': $this->setPhotoAlbum(); break;
case 'setPhotoTags': $this->setPhotoTags(); break;
case 'deletePhoto': $this->deletePhoto(); break;
# Add functions
case 'upload': $this->upload(); break;
case 'importUrl': $this->importUrl(); break;
case 'importServer': $this->importServer(); break;
# Search functions
case 'search': $this->search(); break;
# Session functions
case 'init': $this->init(); break;
case 'login': $this->login(); break;
case 'logout': $this->logout(); break;
# Settings functions
case 'setLogin': $this->setLogin(); break;
case 'setSorting': $this->setSorting(); break;
case 'setDropboxKey': $this->setDropboxKey(); break;
# $_GET functions
case 'getAlbumArchive': $this->getAlbumArchive(); break;
case 'getPhotoArchive': $this->getPhotoArchive(); break;
# Error
default: exit('Error: Function not found! Please check the spelling of the called function.');
return false; break;
}
return true;
}
# Album functions
private function getAlbums() {
$album = new Album($this->database, $this->plugins, $this->settings, null);
echo json_encode($album->getAll(false));
}
private function getAlbum() {
Module::dependencies(isset($_POST['albumID']));
$album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumID']);
echo json_encode($album->get());
}
private function addAlbum() {
Module::dependencies(isset($_POST['title']));
$album = new Album($this->database, $this->plugins, $this->settings, null);
echo $album->add($_POST['title']);
}
private function setAlbumTitle() {
Module::dependencies(isset($_POST['albumIDs'], $_POST['title']));
$album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumIDs']);
echo $album->setTitle($_POST['title']);
}
private function setAlbumDescription() {
Module::dependencies(isset($_POST['albumID'], $_POST['description']));
$album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumID']);
echo $album->setDescription($_POST['description']);
}
private function setAlbumPublic() {
Module::dependencies(isset($_POST['albumID'], $_POST['password']));
$album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumID']);
echo $album->setPublic($_POST['password']);
}
private function setAlbumPassword() {
Module::dependencies(isset($_POST['albumID'], $_POST['password']));
$album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumID']);
echo $album->setPassword($_POST['password']);
}
private function deleteAlbum() {
Module::dependencies(isset($_POST['albumIDs']));
$album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumIDs']);
echo $album->delete($_POST['albumIDs']);
}
# Photo functions
private function getPhoto() {
Module::dependencies(isset($_POST['photoID'], $_POST['albumID']));
$photo = new Photo($this->database, $this->plugins, null, $_POST['photoID']);
echo json_encode($photo->get($_POST['albumID']));
}
private function setPhotoTitle() {
Module::dependencies(isset($_POST['photoIDs'], $_POST['title']));
$photo = new Photo($this->database, $this->plugins, null, $_POST['photoIDs']);
echo $photo->setTitle($_POST['title']);
}
private function setPhotoDescription() {
Module::dependencies(isset($_POST['photoID'], $_POST['description']));
$photo = new Photo($this->database, $this->plugins, null, $_POST['photoID']);
echo $photo->setDescription($_POST['description']);
}
private function setPhotoStar() {
Module::dependencies(isset($_POST['photoIDs']));
$photo = new Photo($this->database, $this->plugins, null, $_POST['photoIDs']);
echo $photo->setStar();
}
private function setPhotoPublic() {
Module::dependencies(isset($_POST['photoID']));
$photo = new Photo($this->database, $this->plugins, null, $_POST['photoID']);
echo $photo->setPublic();
}
private function setPhotoAlbum() {
Module::dependencies(isset($_POST['photoIDs'], $_POST['albumID']));
$photo = new Photo($this->database, $this->plugins, null, $_POST['photoIDs']);
echo $photo->setAlbum($_POST['albumID']);
}
private function setPhotoTags() {
Module::dependencies(isset($_POST['photoIDs'], $_POST['tags']));
$photo = new Photo($this->database, $this->plugins, null, $_POST['photoIDs']);
echo $photo->setTags($_POST['tags']);
}
private function deletePhoto() {
Module::dependencies(isset($_POST['photoIDs']));
$photo = new Photo($this->database, $this->plugins, null, $_POST['photoIDs']);
echo $photo->delete();
}
# Add functions
private function upload() {
Module::dependencies(isset($_FILES, $_POST['albumID']));
$photo = new Photo($this->database, $this->plugins, $this->settings, null);
echo $photo->add($_FILES, $_POST['albumID']);
}
private function importUrl() {
Module::dependencies(isset($_POST['url'], $_POST['albumID']));
echo Import::url($_POST['url'], $_POST['albumID']);
}
private function importServer() {
Module::dependencies(isset($_POST['albumID']));
echo Import::server($_POST['albumID'], null);
}
# Search function
private function search() {
Module::dependencies(isset($_POST['term']));
echo json_encode(search($this->database, $this->settings, $_POST['term']));
}
# Session functions
private function init() {
global $dbName;
Module::dependencies(isset($_POST['version']));
$session = new Session($this->plugins, $this->settings);
echo json_encode($session->init($this->database, $dbName, false, $_POST['version']));
}
private function login() {
Module::dependencies(isset($_POST['user'], $_POST['password']));
$session = new Session($this->plugins, $this->settings);
echo $session->login($_POST['user'], $_POST['password']);
}
private function logout() {
$session = new Session($this->plugins, $this->settings);
echo $session->logout();
}
# Settings functions
private function setLogin() {
Module::dependencies(isset($_POST['username'], $_POST['password']));
if (!isset($_POST['oldPassword'])) $_POST['oldPassword'] = '';
$this->settings = new Settings($this->database);
echo $this->settings->setLogin($_POST['oldPassword'], $_POST['username'], $_POST['password']);
}
private function setSorting() {
Module::dependencies(isset($_POST['type'], $_POST['order']));
$this->settings = new Settings($this->database);
echo $this->settings->setSorting($_POST['type'], $_POST['order']);
}
private function setDropboxKey() {
Module::dependencies(isset($_POST['key']));
$this->settings = new Settings($this->database);
echo $this->settings->setDropboxKey($_POST['key']);
}
# Get functions
private function getAlbumArchive() {
Module::dependencies(isset($_GET['albumID']));
$album = new Album($this->database, $this->plugins, $this->settings, $_GET['albumID']);
$album->getArchive();
}
private function getPhotoArchive() {
Module::dependencies(isset($_GET['photoID']));
$photo = new Photo($this->database, $this->plugins, null, $_GET['photoID']);
$photo->getArchive();
}
}

176
php/access/Guest.php Normal file
View File

@ -0,0 +1,176 @@
<?php
###
# @name Guest Access (Public Mode)
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
if (!defined('LYCHEE_ACCESS_GUEST')) exit('Error: You are not allowed to access this area!');
class Guest extends Access {
public function check($fn) {
switch ($fn) {
# Album functions
case 'getAlbums': $this->getAlbums(); break;
case 'getAlbum': $this->getAlbum(); break;
case 'checkAlbumAccess': $this->checkAlbumAccess(); break;
# Photo functions
case 'getPhoto': $this->getPhoto(); break;
# Session functions
case 'init': $this->init(); break;
case 'login': $this->login(); break;
case 'logout': $this->logout(); break;
# $_GET functions
case 'getAlbumArchive': $this->getAlbumArchive(); break;
case 'getPhotoArchive': $this->getPhotoArchive(); break;
# Error
default: exit('Error: Function not found! Please check the spelling of the called function.');
return false; break;
}
return true;
}
# Album functions
private function getAlbums() {
$album = new Album($this->database, $this->plugins, $this->settings, null);
echo json_encode($album->getAll(true));
}
private function getAlbum() {
Module::dependencies(isset($_POST['albumID'], $_POST['password']));
$album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumID']);
if ($album->getPublic()) {
# Album public
if ($album->checkPassword($_POST['password'])) echo json_encode($album->get());
else echo 'Warning: Wrong password!';
} else {
# Album private
echo 'Warning: Album private!';
}
}
private function checkAlbumAccess() {
Module::dependencies(isset($_POST['albumID'], $_POST['password']));
$album = new Album($this->database, $this->plugins, $this->settings, $_POST['albumID']);
if ($album->getPublic()) {
# Album public
if ($album->checkPassword($_POST['password'])) echo true;
else echo false;
} else {
# Album private
echo false;
}
}
# Photo functions
private function getPhoto() {
Module::dependencies(isset($_POST['photoID'], $_POST['albumID'], $_POST['password']));
$photo = new Photo($this->database, $this->plugins, null, $_POST['photoID']);
if ($photo->getPublic($_POST['password'])) echo json_encode($photo->get($_POST['albumID']));
else echo 'Warning: Wrong password!';
}
# Session functions
private function init() {
global $dbName;
$session = new Session($this->plugins, $this->settings);
echo json_encode($session->init($this->database, $dbName, true, $_POST['version']));
}
private function login() {
Module::dependencies(isset($_POST['user'], $_POST['password']));
$session = new Session($this->plugins, $this->settings);
echo $session->login($_POST['user'], $_POST['password']);
}
private function logout() {
$session = new Session($this->plugins, $this->settings);
echo $session->logout();
}
# $_GET functions
private function getAlbumArchive() {
Module::dependencies(isset($_GET['albumID'], $_GET['password']));
$album = new Album($this->database, $this->plugins, $this->settings, $_GET['albumID']);
if ($album->getPublic()) {
# Album Public
if ($album->checkPassword($_GET['password'])) $album->getArchive();
else exit('Warning: Wrong password!');
} else {
# Album Private
exit('Warning: Album private or not downloadable!');
}
}
private function getPhotoArchive() {
Module::dependencies(isset($_GET['photoID'], $_GET['password']));
$photo = new Photo($this->database, $this->plugins, null, $_GET['photoID']);
# Photo Download
if ($photo->getPublic($_GET['password'])) {
# Photo Public
$photo->getArchive();
} else {
# Photo Private
exit('Warning: Photo private or not downloadable!');
}
}
}
?>

View File

@ -0,0 +1,39 @@
<?php
###
# @name Installation Access
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
if (!defined('LYCHEE_ACCESS_INSTALLATION')) exit('Error: You are not allowed to access this area!');
class Installation extends Access {
public function check($fn) {
switch ($fn) {
case 'dbCreateConfig': $this->dbCreateConfig(); break;
# Error
default: exit('Warning: No configuration!');
return false; break;
}
return true;
}
private function dbCreateConfig() {
Module::dependencies(isset($_POST['dbHost'], $_POST['dbUser'], $_POST['dbPassword'], $_POST['dbName']));
echo Database::createConfig($_POST['dbHost'], $_POST['dbUser'], $_POST['dbPassword'], $_POST['dbName']);
}
}
?>

View File

@ -1,157 +0,0 @@
<?php
/**
* @name Admin Access
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
if (!defined('LYCHEE_ACCESS_ADMIN')) exit('Error: You are not allowed to access this area!');
switch ($_POST['function']) {
// Album Functions
case 'getAlbums': echo json_encode(getAlbums(false));
break;
case 'getAlbum': if (isset($_POST['albumID']))
echo json_encode(getAlbum($_POST['albumID']));
break;
case 'addAlbum': if (isset($_POST['title']))
echo addAlbum($_POST['title']);
break;
case 'setAlbumTitle': if (isset($_POST['albumIDs'], $_POST['title']))
echo setAlbumTitle($_POST['albumIDs'], $_POST['title']);
break;
case 'setAlbumDescription': if (isset($_POST['albumID'], $_POST['description']))
echo setAlbumDescription($_POST['albumID'], $_POST['description']);
break;
case 'setAlbumPublic': if (isset($_POST['albumID']))
if (!isset($_POST['password'])) $_POST['password'] = '';
echo setAlbumPublic($_POST['albumID'], $_POST['password']);
break;
case 'setAlbumPassword': if (isset($_POST['albumID'], $_POST['password']))
echo setAlbumPassword($_POST['albumID'], $_POST['password']);
break;
case 'deleteAlbum': if (isset($_POST['albumIDs']))
echo deleteAlbum($_POST['albumIDs']);
break;
// Photo Functions
case 'getPhoto': if (isset($_POST['photoID'], $_POST['albumID']))
echo json_encode(getPhoto($_POST['photoID'], $_POST['albumID']));
break;
case 'deletePhoto': if (isset($_POST['photoIDs']))
echo deletePhoto($_POST['photoIDs']);
break;
case 'setPhotoAlbum': if (isset($_POST['photoIDs'], $_POST['albumID']))
echo setPhotoAlbum($_POST['photoIDs'], $_POST['albumID']);
break;
case 'setPhotoTitle': if (isset($_POST['photoIDs'], $_POST['title']))
echo setPhotoTitle($_POST['photoIDs'], $_POST['title']);
break;
case 'setPhotoStar': if (isset($_POST['photoIDs']))
echo setPhotoStar($_POST['photoIDs']);
break;
case 'setPhotoPublic': if (isset($_POST['photoID'], $_POST['url']))
echo setPhotoPublic($_POST['photoID'], $_POST['url']);
break;
case 'setPhotoDescription': if (isset($_POST['photoID'], $_POST['description']))
echo setPhotoDescription($_POST['photoID'], $_POST['description']);
break;
case 'setPhotoTags': if (isset($_POST['photoIDs'], $_POST['tags']))
echo setPhotoTags($_POST['photoIDs'], $_POST['tags']);
break;
// Add Functions
case 'upload': if (isset($_FILES, $_POST['albumID']))
echo upload($_FILES, $_POST['albumID']);
break;
case 'importUrl': if (isset($_POST['url'], $_POST['albumID']))
echo importUrl($_POST['url'], $_POST['albumID']);
break;
case 'importServer': if (isset($_POST['albumID']))
echo importServer($_POST['albumID']);
break;
// Search Function
case 'search': if (isset($_POST['term']))
echo json_encode(search($_POST['term']));
break;
// Session Function
case 'init': echo json_encode(init('admin', $_POST['version']));
break;
case 'login': if (isset($_POST['user'], $_POST['password']))
echo login($_POST['user'], $_POST['password']);
break;
case 'logout': logout();
break;
// Settings
case 'setLogin': if (isset($_POST['username'], $_POST['password']))
if (!isset($_POST['oldPassword'])) $_POST['oldPassword'] = '';
echo setLogin($_POST['oldPassword'], $_POST['username'], $_POST['password']);
break;
case 'setSorting': if (isset($_POST['type'], $_POST['order']))
echo setSorting($_POST['type'], $_POST['order']);
break;
case 'setDropboxKey': if (isset($_POST['key']))
echo setDropboxKey($_POST['key']);
break;
// Miscellaneous
default: switch ($_GET['function']) {
case 'getFeed': if (isset($_GET['albumID']))
echo getFeed($_GET['albumID']);
break;
case 'getAlbumArchive': if (isset($_GET['albumID']))
getAlbumArchive($_GET['albumID']);
break;
case 'getPhotoArchive': if (isset($_GET['photoID']))
getPhotoArchive($_GET['photoID']);
break;
case 'update': echo update();
break;
default: exit('Error: Function not found! Please check the spelling of the called function.');
break;
}
break;
}
?>

View File

@ -1,126 +0,0 @@
<?php
/**
* @name Guest Access (Public Mode)
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
if (!defined('LYCHEE_ACCESS_GUEST')) exit('Error: You are not allowed to access this area!');
switch ($_POST['function']) {
// Album Functions
case 'getAlbums': echo json_encode(getAlbums(true));
break;
case 'getAlbum': if (isset($_POST['albumID'], $_POST['password'])) {
if (isAlbumPublic($_POST['albumID'])) {
// Album Public
if (checkAlbumPassword($_POST['albumID'], $_POST['password']))
echo json_encode(getAlbum($_POST['albumID']));
else
echo 'Warning: Wrong password!';
} else {
// Album Private
echo 'Warning: Album private!';
}
}
break;
case 'checkAlbumAccess':if (isset($_POST['albumID'], $_POST['password'])) {
if (isAlbumPublic($_POST['albumID'])) {
// Album Public
if (checkAlbumPassword($_POST['albumID'], $_POST['password']))
echo true;
else
echo false;
} else {
// Album Private
echo false;
}
}
break;
// Photo Functions
case 'getPhoto': if (isset($_POST['photoID'], $_POST['albumID'], $_POST['password'])) {
if (isPhotoPublic($_POST['photoID'], $_POST['password']))
echo json_encode(getPhoto($_POST['photoID'], $_POST['albumID']));
else
echo 'Warning: Wrong password!';
}
break;
// Session Functions
case 'init': echo json_encode(init('public', $_POST['version']));
break;
case 'login': if (isset($_POST['user'], $_POST['password']))
echo login($_POST['user'], $_POST['password']);
break;
// Miscellaneous
default: switch ($_GET['function']) {
case 'getFeed': if (isset($_GET['albumID'], $_GET['password'])) {
// Album Feed
if (isAlbumPublic($_GET['albumID'])) {
// Album Public
if (checkAlbumPassword($_GET['albumID'], $_GET['password']))
echo getFeed($_GET['albumID']);
else
exit('Warning: Wrong password!');
} else {
// Album Private
exit('Warning: Album private!');
}
}
break;
case 'getAlbumArchive': if (isset($_GET['albumID'], $_GET['password'])) {
// Album Download
if (isAlbumPublic($_GET['albumID'])) {
// Album Public
if (checkAlbumPassword($_GET['albumID'], $_GET['password']))
getAlbumArchive($_GET['albumID']);
else
exit('Warning: Wrong password!');
} else {
// Album Private
exit('Warning: Album private or not downloadable!');
}
}
break;
case 'getPhotoArchive': if (isset($_GET['photoID'], $_GET['password'])) {
// Photo Download
if (isPhotoPublic($_GET['photoID'], $_GET['password']))
// Photo Public
getPhotoArchive($_GET['photoID']);
else
// Photo Private
exit('Warning: Photo private or not downloadable!');
}
break;
default: exit('Error: Function not found! Please check the spelling of the called function.');
break;
}
break;
}
?>

View File

@ -1,23 +0,0 @@
<?php
/**
* @name Installation Access
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
if (!defined('LYCHEE_ACCESS_INSTALLATION')) exit('Error: You are not allowed to access this area!');
switch ($_POST['function']) {
case 'dbCreateConfig': if (isset($_POST['dbHost'], $_POST['dbUser'], $_POST['dbPassword'], $_POST['dbName'], $_POST['version']))
echo dbCreateConfig($_POST['dbHost'], $_POST['dbUser'], $_POST['dbPassword'], $_POST['dbName'], $_POST['version']);
break;
default: echo 'Warning: No configuration!';
break;
}
?>

View File

@ -1,10 +1,10 @@
<?php
/**
* @name API
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
###
# @name API
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
@ini_set('max_execution_time', '200');
@ini_set('post_max_size', '200M');
@ -15,75 +15,88 @@
if (!empty($_POST['function'])||!empty($_GET['function'])) {
session_start();
define('LYCHEE', true);
date_default_timezone_set('UTC');
// Load modules
require('modules/album.php');
require('modules/db.php');
require('modules/misc.php');
require('modules/photo.php');
require('modules/session.php');
require('modules/settings.php');
require('modules/upload.php');
# Define globals
require(__DIR__ . '/define.php');
if (file_exists('../data/config.php')) require('../data/config.php');
# Load autoload
require(__DIR__ . '/autoload.php');
# Load modules
require(__DIR__ . '/modules/misc.php');
if (file_exists(LYCHEE_CONFIG_FILE)) require(LYCHEE_CONFIG_FILE);
else {
/**
* Installation Access
* Limited access to configure Lychee. Only available when the config.php file is missing.
*/
###
# Installation Access
# Limited access to configure Lychee. Only available when the config.php file is missing.
###
define('LYCHEE_ACCESS_INSTALLATION', true);
require('access/installation.php');
$installation = new Installation(null, null, null);
$installation->check($_POST['function']);
exit();
}
// Connect and get settings
$database = dbConnect();
$settings = getSettings();
# Connect to database
$database = Database::connect($dbHost, $dbUser, $dbPassword, $dbName);
// Escape
# Load settings
$settings = new Settings($database);
$settings = $settings->get();
# Init plugins
$plugins = explode(';', $settings['plugins']);
$plugins = new Plugins($plugins, $database, $settings);
# Escape
foreach(array_keys($_POST) as $key) $_POST[$key] = mysqli_real_escape_string($database, urldecode($_POST[$key]));
foreach(array_keys($_GET) as $key) $_GET[$key] = mysqli_real_escape_string($database, urldecode($_GET[$key]));
// Validate parameters
# Validate parameters
if (isset($_POST['albumIDs'])&&preg_match('/^[0-9\,]{1,}$/', $_POST['albumIDs'])!==1) exit('Error: Wrong parameter type for albumIDs!');
if (isset($_POST['photoIDs'])&&preg_match('/^[0-9\,]{1,}$/', $_POST['photoIDs'])!==1) exit('Error: Wrong parameter type for photoIDs!');
if (isset($_POST['albumID'])&&preg_match('/^[0-9sf]{1,}$/', $_POST['albumID'])!==1) exit('Error: Wrong parameter type for albumID!');
if (isset($_POST['photoID'])&&preg_match('/^[0-9]{14}$/', $_POST['photoID'])!==1) exit('Error: Wrong parameter type for photoID!');
// Fallback for switch statement
if (!isset($_POST['function'])) $_POST['function'] = '';
if (!isset($_GET['function'])) $_GET['function'] = '';
# Function for switch statement
if (isset($_POST['function'])) $fn = $_POST['function'];
else $fn = $_GET['function'];
if (isset($_SESSION['login'])&&$_SESSION['login']==true) {
/**
* Admin Access
* Full access to Lychee. Only with correct password/session.
*/
###
# Admin Access
# Full access to Lychee. Only with correct password/session.
###
define('LYCHEE_ACCESS_ADMIN', true);
require('access/admin.php');
$admin = new Admin($database, $plugins, $settings);
$admin->check($fn);
} else {
/**
* Guest Access
* Access to view all public folders and photos in Lychee.
*/
###
# Guest Access
# Access to view all public folders and photos in Lychee.
###
define('LYCHEE_ACCESS_GUEST', true);
require('access/guest.php');
$guest = new Guest($database, $plugins, $settings);
$guest->check($fn);
}
} else {
exit('Error: No permission!');
exit('Error: Called function not found!');
}

28
php/autoload.php Normal file
View File

@ -0,0 +1,28 @@
<?php
###
# @name Autoload
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
function lycheeAutoloaderModules($class_name) {
$file = LYCHEE . 'php/modules/' . $class_name . '.php';
if (file_exists($file)!==false) require $file;
}
function lycheeAutoloaderAccess($class_name) {
$file = LYCHEE . 'php/access/' . $class_name . '.php';
if (file_exists($file)!==false) require $file;
}
spl_autoload_register('lycheeAutoloaderModules');
spl_autoload_register('lycheeAutoloaderAccess');
?>

View File

@ -0,0 +1,14 @@
# Dump of table lychee_albums
# Version 2.5
# ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `lychee_albums` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(50) NOT NULL,
`description` varchar(1000) DEFAULT '',
`sysstamp` int(11) NOT NULL,
`public` tinyint(1) NOT NULL DEFAULT '0',
`visible` tinyint(1) NOT NULL DEFAULT '1',
`password` varchar(100) DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

View File

@ -0,0 +1,13 @@
# Dump of table lychee_log
# Version 2.5
# ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `lychee_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`time` int(11) NOT NULL,
`type` varchar(11) NOT NULL,
`function` varchar(100) NOT NULL,
`line` int(11) NOT NULL,
`text` TEXT,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

View File

@ -0,0 +1,27 @@
# Dump of table lychee_photos
# Version 2.5
# ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `lychee_photos` (
`id` bigint(14) NOT NULL,
`title` varchar(50) NOT NULL,
`description` varchar(1000) DEFAULT '',
`url` varchar(100) NOT NULL,
`tags` varchar(1000) NOT NULL DEFAULT '',
`public` tinyint(1) NOT NULL,
`type` varchar(10) NOT NULL,
`width` int(11) NOT NULL,
`height` int(11) NOT NULL,
`size` varchar(20) NOT NULL,
`iso` varchar(15) NOT NULL,
`aperture` varchar(20) NOT NULL,
`make` varchar(50) NOT NULL,
`model` varchar(50) NOT NULL,
`shutter` varchar(30) NOT NULL,
`focal` varchar(20) NOT NULL,
`takestamp` int(11) DEFAULT NULL,
`star` tinyint(1) NOT NULL,
`thumbUrl` varchar(50) NOT NULL,
`album` varchar(30) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

View File

@ -0,0 +1,14 @@
# Content of table lychee_settings
# Version 2.5
# ------------------------------------------------------------
INSERT INTO `lychee_settings` (`key`, `value`)
VALUES
('version',''),
('username',''),
('password',''),
('thumbQuality','90'),
('checkForUpdates','1'),
('sorting','ORDER BY id DESC'),
('dropboxKey',''),
('plugins','');

View File

@ -0,0 +1,8 @@
# Dump of table lychee_settings
# Version 2.5
# ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `lychee_settings` (
`key` varchar(50) NOT NULL DEFAULT '',
`value` varchar(200) DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

View File

@ -0,0 +1,41 @@
<?php
###
# @name Update to version 2.1
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if(!$database->query("SELECT `tags` FROM `lychee_photos` LIMIT 1;")) {
$result = $database->query("ALTER TABLE `lychee_photos` ADD `tags` VARCHAR( 1000 ) NULL DEFAULT ''");
if (!$result) {
Log::error($database, 'update_020100', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
}
$result = $database->query("SELECT `key` FROM `lychee_settings` WHERE `key` = 'dropboxKey' LIMIT 1;");
if ($result->num_rows===0) {
$result = $database->query("INSERT INTO `lychee_settings` (`key`, `value`) VALUES ('dropboxKey', '')");
if (!$result) {
Log::error($database, 'update_020100', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
}
$result = $database->query("SELECT `key` FROM `lychee_settings` WHERE `key` = 'version' LIMIT 1;");
if ($result->num_rows===0) {
$result = $database->query("INSERT INTO `lychee_settings` (`key`, `value`) VALUES ('version', '020100')");
if (!$result) {
Log::error($database, 'update_020100', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
} else {
$result = $database->query("UPDATE lychee_settings SET value = '020100' WHERE `key` = 'version';");
if (!$result) {
Log::error($database, 'update_020100', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
}
?>

View File

@ -0,0 +1,21 @@
<?php
###
# @name Update to version 2.1.1
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
$result = $database->query("ALTER TABLE `lychee_settings` CHANGE `value` `value` VARCHAR( 200 ) NULL DEFAULT ''");
if (!$result) {
Log::error($database, 'update_020101', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
$result = $database->query("UPDATE lychee_settings SET value = '020101' WHERE `key` = 'version';");
if (!$result) {
Log::error($database, 'update_020101', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
?>

View File

@ -0,0 +1,23 @@
<?php
###
# @name Update to version 2.2
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!$database->query("SELECT `visible` FROM `lychee_albums` LIMIT 1;")) {
$result = $database->query("ALTER TABLE `lychee_albums` ADD `visible` TINYINT(1) NOT NULL DEFAULT 1");
if (!$result) {
Log::error($database, 'update_020200', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
}
$result = $database->query("UPDATE lychee_settings SET value = '020200' WHERE `key` = 'version';");
if (!$result) {
Log::error($database, 'update_020200', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
?>

View File

@ -0,0 +1,133 @@
<?php
###
# @name Update to version 2.5
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
# Add `plugins`
$result = $database->query("SELECT `key` FROM `lychee_settings` WHERE `key` = 'plugins' LIMIT 1;");
if ($result->num_rows===0) {
$result = $database->query("INSERT INTO `lychee_settings` (`key`, `value`) VALUES ('plugins', '')");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
}
# Add `takestamp`
if (!$database->query("SELECT `takestamp` FROM `lychee_photos` LIMIT 1;")) {
$result = $database->query("ALTER TABLE `lychee_photos` ADD `takestamp` INT(11) DEFAULT NULL");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
}
# Convert to `takestamp`
if ($database->query("SELECT `takedate`, `taketime` FROM `lychee_photos` LIMIT 1;")) {
$result = $database->query("SELECT `id`, `takedate`, `taketime` FROM `lychee_photos` WHERE `takedate` <> '' AND `taketime` <> '';");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
while ($photo = $result->fetch_object()) {
$takestamp = strtotime($photo->takedate . $photo->taketime);
$database->query("UPDATE `lychee_photos` SET `takestamp` = '$takestamp' WHERE `id` = '$photo->id';");
}
$result = $database->query("ALTER TABLE `lychee_photos` DROP COLUMN `takedate`;");
$result = $database->query("ALTER TABLE `lychee_photos` DROP COLUMN `taketime`;");
}
# Remove `import_name`
if ($database->query("SELECT `import_name` FROM `lychee_photos` LIMIT 1;")) {
$result = $database->query("ALTER TABLE `lychee_photos` DROP COLUMN `import_name`;");
}
# Remove `sysdate` and `systime`
if ($database->query("SELECT `sysdate`, `systime` FROM `lychee_photos` LIMIT 1;")) {
$result = $database->query("ALTER TABLE `lychee_photos` DROP COLUMN `sysdate`;");
$result = $database->query("ALTER TABLE `lychee_photos` DROP COLUMN `systime`;");
}
# Add `sysstamp`
if (!$database->query("SELECT `sysstamp` FROM `lychee_albums` LIMIT 1;")) {
$result = $database->query("ALTER TABLE `lychee_albums` ADD `sysstamp` INT(11) DEFAULT NULL");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
}
# Convert to `sysstamp`
if ($database->query("SELECT `sysdate` FROM `lychee_albums` LIMIT 1;")) {
$result = $database->query("SELECT `id`, `sysdate` FROM `lychee_albums`;");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
while ($album = $result->fetch_object()) {
$sysstamp = strtotime($album->sysdate);
$database->query("UPDATE `lychee_albums` SET `sysstamp` = '$sysstamp' WHERE `id` = '$album->id';");
}
$result = $database->query("ALTER TABLE `lychee_albums` DROP COLUMN `sysdate`;");
}
# Set character of database
$result = $database->query("ALTER DATABASE $dbName CHARACTER SET utf8 COLLATE utf8_general_ci;");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
# Set character
$result = $database->query("ALTER TABLE `lychee_albums` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
# Set character
$result = $database->query("ALTER TABLE `lychee_photos` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
# Set character
$result = $database->query("ALTER TABLE `lychee_settings` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
# Set album password length to 100 (for longer hashes)
$result = $database->query("ALTER TABLE `lychee_albums` CHANGE `password` `password` VARCHAR(100);");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
# Set make length to 50
$result = $database->query("ALTER TABLE `lychee_photos` CHANGE `make` `make` VARCHAR(50);");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
# Reset sorting
$result = $database->query("UPDATE lychee_settings SET value = 'ORDER BY takestamp DESC' WHERE `key` = 'sorting' AND `value` LIKE '%UNIX_TIMESTAMP%';");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
# Set version
$result = $database->query("UPDATE lychee_settings SET value = '020500' WHERE `key` = 'version';");
if (!$result) {
Log::error($database, 'update_020500', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
?>

24
php/define.php Normal file
View File

@ -0,0 +1,24 @@
<?php
###
# @name Define
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
# Define root
define('LYCHEE', substr(__DIR__, 0, -3));
# Define dirs
define('LYCHEE_DATA', LYCHEE . 'data/');
define('LYCHEE_UPLOADS', LYCHEE . 'uploads/');
define('LYCHEE_UPLOADS_BIG', LYCHEE_UPLOADS . 'big/');
define('LYCHEE_UPLOADS_MEDIUM', LYCHEE_UPLOADS . 'medium/');
define('LYCHEE_UPLOADS_THUMB', LYCHEE_UPLOADS . 'thumb/');
define('LYCHEE_UPLOADS_IMPORT', LYCHEE_UPLOADS . 'import/');
define('LYCHEE_PLUGINS', LYCHEE . 'plugins/');
# Define files
define('LYCHEE_CONFIG_FILE', LYCHEE_DATA . 'config.php');
?>

557
php/modules/Album.php Normal file
View File

@ -0,0 +1,557 @@
<?php
###
# @name Album Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Album extends Module {
private $database = null;
private $settings = null;
private $albumIDs = null;
public function __construct($database, $plugins, $settings, $albumIDs) {
# Init vars
$this->database = $database;
$this->plugins = $plugins;
$this->settings = $settings;
$this->albumIDs = $albumIDs;
return true;
}
public function add($title = 'Untitled', $public = 0, $visible = 1) {
# Check dependencies
$this->dependencies(isset($this->database));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Parse
if (strlen($title)>50) $title = substr($title, 0, 50);
# Database
$sysstamp = time();
$result = $this->database->query("INSERT INTO lychee_albums (title, sysstamp, public, visible) VALUES ('$title', '$sysstamp', '$public', '$visible');");
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return $this->database->insert_id;
}
public function get() {
# Check dependencies
$this->dependencies(isset($this->database, $this->settings, $this->albumIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Get album information
switch ($this->albumIDs) {
case 'f': $return['public'] = false;
$query = "SELECT id, title, tags, public, star, album, thumbUrl FROM lychee_photos WHERE star = 1 " . $this->settings['sorting'];
break;
case 's': $return['public'] = false;
$query = "SELECT id, title, tags, public, star, album, thumbUrl FROM lychee_photos WHERE public = 1 " . $this->settings['sorting'];
break;
case '0': $return['public'] = false;
$query = "SELECT id, title, tags, public, star, album, thumbUrl FROM lychee_photos WHERE album = 0 " . $this->settings['sorting'];
break;
default: $albums = $this->database->query("SELECT * FROM lychee_albums WHERE id = '$this->albumIDs' LIMIT 1;");
$return = $albums->fetch_assoc();
$return['sysdate'] = date('d M. Y', $return['sysstamp']);
$return['password'] = ($return['password']=='' ? false : true);
$query = "SELECT id, title, tags, public, star, album, thumbUrl FROM lychee_photos WHERE album = '$this->albumIDs' " . $this->settings['sorting'];
break;
}
# Get photos
$photos = $this->database->query($query);
$previousPhotoID = '';
while ($photo = $photos->fetch_assoc()) {
# Parse
$photo['sysdate'] = date('d F Y', substr($photo['id'], 0, -4));
$photo['previousPhoto'] = $previousPhotoID;
$photo['nextPhoto'] = '';
if ($previousPhotoID!=='') $return['content'][$previousPhotoID]['nextPhoto'] = $photo['id'];
$previousPhotoID = $photo['id'];
# Add to return
$return['content'][$photo['id']] = $photo;
}
if ($photos->num_rows===0) {
# Album empty
$return['content'] = false;
} else {
# Enable next and previous for the first and last photo
$lastElement = end($return['content']);
$lastElementId = $lastElement['id'];
$firstElement = reset($return['content']);
$firstElementId = $firstElement['id'];
if ($lastElementId!==$firstElementId) {
$return['content'][$lastElementId]['nextPhoto'] = $firstElementId;
$return['content'][$firstElementId]['previousPhoto'] = $lastElementId;
}
}
$return['id'] = $this->albumIDs;
$return['num'] = $photos->num_rows;
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return $return;
}
public function getAll($public) {
# Check dependencies
$this->dependencies(isset($this->database, $this->settings, $public));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Get SmartAlbums
if ($public===false) $return = $this->getSmartInfo();
# Albums query
$query = 'SELECT id, title, public, sysstamp, password FROM lychee_albums WHERE public = 1 AND visible <> 0';
if ($public===false) $query = 'SELECT id, title, public, sysstamp, password FROM lychee_albums';
# Execute query
$albums = $this->database->query($query) OR exit('Error: ' . $this->database->error);
# For each album
while ($album = $albums->fetch_assoc()) {
# Parse info
$album['sysdate'] = date('F Y', $album['sysstamp']);
$album['password'] = ($album['password'] != '');
# Thumbs
if (($public===true&&$album['password']===false)||($public===false)) {
# Execute query
$thumbs = $this->database->query("SELECT thumbUrl FROM lychee_photos WHERE album = '" . $album['id'] . "' ORDER BY star DESC, " . substr($this->settings['sorting'], 9) . " LIMIT 3");
# For each thumb
$k = 0;
while ($thumb = $thumbs->fetch_object()) {
$album["thumb$k"] = $thumb->thumbUrl;
$k++;
}
}
# Add to return
$return['content'][$album['id']] = $album;
}
# Num of albums
$return['num'] = $albums->num_rows;
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return $return;
}
private function getSmartInfo() {
# Check dependencies
$this->dependencies(isset($this->database, $this->settings));
# Unsorted
$unsorted = $this->database->query("SELECT thumbUrl FROM lychee_photos WHERE album = 0 " . $this->settings['sorting']);
$i = 0;
while($row = $unsorted->fetch_object()) {
if ($i<3) {
$return["unsortedThumb$i"] = $row->thumbUrl;
$i++;
} else break;
}
$return['unsortedNum'] = $unsorted->num_rows;
# Public
$public = $this->database->query("SELECT thumbUrl FROM lychee_photos WHERE public = 1 " . $this->settings['sorting']);
$i = 0;
while($row2 = $public->fetch_object()) {
if ($i<3) {
$return["publicThumb$i"] = $row2->thumbUrl;
$i++;
} else break;
}
$return['publicNum'] = $public->num_rows;
# Starred
$starred = $this->database->query("SELECT thumbUrl FROM lychee_photos WHERE star = 1 " . $this->settings['sorting']);
$i = 0;
while($row3 = $starred->fetch_object()) {
if ($i<3) {
$return["starredThumb$i"] = $row3->thumbUrl;
$i++;
} else break;
}
$return['starredNum'] = $starred->num_rows;
return $return;
}
public function getArchive() {
# Check dependencies
$this->dependencies(isset($this->database, $this->albumIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Illicit chars
$badChars = array_merge(
array_map('chr', range(0,31)),
array("<", ">", ":", '"', "/", "\\", "|", "?", "*")
);
# Photos query
switch($this->albumIDs) {
case 's':
$photos = "SELECT title, url FROM lychee_photos WHERE public = '1';";
$zipTitle = 'Public';
break;
case 'f':
$photos = "SELECT title, url FROM lychee_photos WHERE star = '1';";
$zipTitle = 'Starred';
break;
default:
$photos = "SELECT title, url FROM lychee_photos WHERE album = '$this->albumIDs';";
$zipTitle = 'Unsorted';
}
# Set title
$album = $this->database->query("SELECT title FROM lychee_albums WHERE id = '$this->albumIDs' LIMIT 1;");
if ($this->albumIDs!=0&&is_numeric($this->albumIDs)) $zipTitle = $album->fetch_object()->title;
# Parse title
$zipTitle = str_replace($badChars, '', $zipTitle);
$filename = LYCHEE_DATA . $zipTitle . '.zip';
# Create zip
$zip = new ZipArchive();
if ($zip->open($filename, ZIPARCHIVE::CREATE)!==TRUE) {
Log::error($this->database, __METHOD__, __LINE__, 'Could not create ZipArchive');
return false;
}
# Execute query
$photos = $this->database->query($photos);
# Check if album empty
if ($photos->num_rows==0) {
Log::error($this->database, __METHOD__, __LINE__, 'Could not create ZipArchive without images');
return false;
}
# Parse each path
$files = array();
while ($photo = $photos->fetch_object()) {
# Parse url
$photo->url = LYCHEE_UPLOADS_BIG . $photo->url;
# Parse title
$photo->title = str_replace($badChars, '', $photo->title);
if (!isset($photo->title)||$photo->title==='') $photo->title = 'Untitled';
# Check if readable
if (!@is_readable($photo->url)) continue;
# Get extension of image
$extension = getExtension($photo->url);
# Set title for photo
$zipFileName = $zipTitle . '/' . $photo->title . $extension;
# Check for duplicates
if (!empty($files)) {
$i = 1;
while (in_array($zipFileName, $files)) {
# Set new title for photo
$zipFileName = $zipTitle . '/' . $photo->title . '-' . $i . $extension;
$i++;
}
}
# Add to array
$files[] = $zipFileName;
# Add photo to zip
$zip->addFile($photo->url, $zipFileName);
}
# Finish zip
$zip->close();
# Send zip
header("Content-Type: application/zip");
header("Content-Disposition: attachment; filename=\"$zipTitle.zip\"");
header("Content-Length: " . filesize($filename));
readfile($filename);
# Delete zip
unlink($filename);
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return true;
}
public function setTitle($title = 'Untitled') {
# Check dependencies
$this->dependencies(isset($this->database, $this->albumIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Parse
if (strlen($title)>50) $title = substr($title, 0, 50);
# Execute query
$result = $this->database->query("UPDATE lychee_albums SET title = '$title' WHERE id IN ($this->albumIDs);");
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
public function setDescription($description = '') {
# Check dependencies
$this->dependencies(isset($this->database, $this->albumIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Parse
$description = htmlentities($description);
if (strlen($description)>1000) $description = substr($description, 0, 1000);
# Execute query
$result = $this->database->query("UPDATE lychee_albums SET description = '$description' WHERE id IN ($this->albumIDs);");
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
public function getPublic() {
# Check dependencies
$this->dependencies(isset($this->database, $this->albumIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
if ($this->albumIDs==='0'||$this->albumIDs==='s'||$this->albumIDs==='f') return false;
# Execute query
$albums = $this->database->query("SELECT public FROM lychee_albums WHERE id = '$this->albumIDs' LIMIT 1;");
$album = $albums->fetch_object();
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if ($album->public==1) return true;
return false;
}
public function setPublic($password) {
# Check dependencies
$this->dependencies(isset($this->database, $this->albumIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Get public
$albums = $this->database->query("SELECT id, public FROM lychee_albums WHERE id IN ('$this->albumIDs');");
while ($album = $albums->fetch_object()) {
# Invert public
$public = ($album->public=='0' ? 1 : 0);
# Set public
$result = $this->database->query("UPDATE lychee_albums SET public = '$public', visible = 1, password = NULL WHERE id = '$album->id';");
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
# Reset permissions for photos
if ($public===1) {
$result = $this->database->query("UPDATE lychee_photos SET public = 0 WHERE album = '$album->id';");
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
}
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
# Set password
if (isset($password)&&strlen($password)>0) return $this->setPassword($password);
return true;
}
public function setPassword($password) {
# Check dependencies
$this->dependencies(isset($this->database, $this->albumIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
if (strlen($password)>0) {
# Get hashed password
$password = get_hashed_password($password);
# Set hashed password
$result = $this->database->query("UPDATE lychee_albums SET visible = 0, password = '$password' WHERE id IN ('$this->albumIDs');");
} else {
# Unset password
$result = $this->database->query("UPDATE lychee_albums SET visible = 1, password = NULL WHERE id IN ('$this->albumIDs');");
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
public function checkPassword($password) {
# Check dependencies
$this->dependencies(isset($this->database, $this->albumIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Execute query
$albums = $this->database->query("SELECT password FROM lychee_albums WHERE id = '$this->albumIDs' LIMIT 1;");
$album = $albums->fetch_object();
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if ($album->password=='') return true;
else if ($album->password===$password||$album->password===crypt($password, $album->password)) return true;
return false;
}
public function delete($albumIDs) {
# Check dependencies
$this->dependencies(isset($this->database, $this->albumIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Init vars
$error = false;
# Execute query
$photos = $this->database->query("SELECT id FROM lychee_photos WHERE album IN ($albumIDs);");
# For each album delete photo
while ($row = $photos->fetch_object()) {
$photo = new Photo($this->database, $this->plugins, null, $row->id);
if (!$photo->delete($row->id)) $error = true;
}
# Delete albums
$result = $this->database->query("DELETE FROM lychee_albums WHERE id IN ($albumIDs);");
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if ($error) return false;
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
}
?>

212
php/modules/Database.php Executable file
View File

@ -0,0 +1,212 @@
<?php
###
# @name Database Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Database extends Module {
static function connect($host = 'localhost', $user, $password, $name = 'lychee') {
# Check dependencies
Module::dependencies(isset($host, $user, $password, $name));
$database = new mysqli($host, $user, $password);
# Check connection
if ($database->connect_errno) exit('Error: ' . $database->connect_error);
# Avoid sql injection on older MySQL versions by using GBK
if ($database->server_version<50500) $database->set_charset('GBK');
else $database->set_charset("utf8");
# Check database
if (!$database->select_db($name))
if (!Database::createDatabase($database, $name)) exit('Error: Could not create database!');
# Check tables
if (!$database->query('SELECT * FROM lychee_photos, lychee_albums, lychee_settings, lychee_log LIMIT 0;'))
if (!Database::createTables($database)) exit('Error: Could not create tables!');
return $database;
}
static function update($database, $dbName, $version = 0) {
# Check dependencies
Module::dependencies(isset($database, $dbName));
# List of updates
$updates = array(
'020100', #2.1
'020101', #2.1.1
'020200', #2.2
'020500' #2.5
);
# For each update
foreach ($updates as $update) {
if (isset($version)&&$update<=$version) continue;
# Load update
include(__DIR__ . '/../database/update_' . $update . '.php');
}
return true;
}
static function createConfig($host = 'localhost', $user, $password, $name = 'lychee') {
# Check dependencies
Module::dependencies(isset($host, $user, $password, $name));
$database = new mysqli($host, $user, $password);
if ($database->connect_errno) return 'Warning: Connection failed!';
else {
$config = "<?php
###
# @name Configuration
# @author Tobias Reich
# @copyright 2014 Tobias Reich
###
if(!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
# Database configuration
\$dbHost = '$host'; # Host of the database
\$dbUser = '$user'; # Username of the database
\$dbPassword = '$password'; # Password of the database
\$dbName = '$name'; # Database name
?>";
# Save file
if (file_put_contents(LYCHEE_CONFIG_FILE, $config)===false) return 'Warning: Could not create file!';
return true;
}
}
static function createDatabase($database, $name = 'lychee') {
# Check dependencies
Module::dependencies(isset($database, $name));
# Create database
$result = $database->query("CREATE DATABASE IF NOT EXISTS $name;");
$database->select_db($name);
if (!$database->select_db($name)||!$result) return false;
return true;
}
static function createTables($database) {
# Check dependencies
Module::dependencies(isset($database));
# Create log
if (!$database->query('SELECT * FROM lychee_log LIMIT 0;')) {
# Read file
$file = __DIR__ . '/../database/log_table.sql';
$query = @file_get_contents($file);
# Create table
if (!isset($query)||$query===false) return false;
if (!$database->query($query)) return false;
}
# Create settings
if (!$database->query('SELECT * FROM lychee_settings LIMIT 0;')) {
# Read file
$file = __DIR__ . '/../database/settings_table.sql';
$query = @file_get_contents($file);
# Create table
if (!isset($query)||$query===false) {
Log::error($database, __METHOD__, __LINE__, 'Could not load query for lychee_settings');
return false;
}
if (!$database->query($query)) {
Log::error($database, __METHOD__, __LINE__, $database->error);
return false;
}
# Read file
$file = __DIR__ . '/../database/settings_content.sql';
$query = @file_get_contents($file);
# Add content
if (!isset($query)||$query===false) {
Log::error($database, __METHOD__, __LINE__, 'Could not load content-query for lychee_settings');
return false;
}
if (!$database->query($query)) {
Log::error($database, __METHOD__, __LINE__, $database->error);
return false;
}
}
# Create albums
if (!$database->query('SELECT * FROM lychee_albums LIMIT 0;')) {
# Read file
$file = __DIR__ . '/../database/albums_table.sql';
$query = @file_get_contents($file);
# Create table
if (!isset($query)||$query===false) {
Log::error($database, __METHOD__, __LINE__, 'Could not load query for lychee_albums');
return false;
}
if (!$database->query($query)) {
Log::error($database, __METHOD__, __LINE__, $database->error);
return false;
}
}
# Create photos
if (!$database->query('SELECT * FROM lychee_photos LIMIT 0;')) {
# Read file
$file = __DIR__ . '/../database/photos_table.sql';
$query = @file_get_contents($file);
# Create table
if (!isset($query)||$query===false) {
Log::error($database, __METHOD__, __LINE__, 'Could not load query for lychee_photos');
return false;
}
if (!$database->query($query)) {
Log::error($database, __METHOD__, __LINE__, $database->error);
return false;
}
}
return true;
}
}
?>

197
php/modules/Import.php Normal file
View File

@ -0,0 +1,197 @@
<?php
###
# @name Upload Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Import extends Module {
static function photo($database, $plugins, $settings, $path, $albumID = 0, $description = '', $tags = '') {
$info = getimagesize($path);
$size = filesize($path);
$photo = new Photo($database, $plugins, $settings, null);
$nameFile = array(array());
$nameFile[0]['name'] = $path;
$nameFile[0]['type'] = $info['mime'];
$nameFile[0]['tmp_name'] = $path;
$nameFile[0]['error'] = 0;
$nameFile[0]['size'] = $size;
if (!$photo->add($nameFile, $albumID, $description, $tags)) return false;
return true;
}
static function url($urls, $albumID = 0) {
$error = false;
# Parse
$urls = str_replace(' ', '%20', $urls);
$urls = explode(',', $urls);
foreach ($urls as &$url) {
if (@exif_imagetype($url)===false) {
$error = true;
continue;
}
$pathinfo = pathinfo($url);
$filename = $pathinfo['filename'] . '.' . $pathinfo['extension'];
$tmp_name = LYCHEE_DATA . $filename;
if (@copy($url, $tmp_name)===false) $error = true;
}
$import = Import::server($albumID, LYCHEE_DATA);
if ($error===false&&$import===true) return true;
else return false;
}
static function move($database, $path) {
# Determine OS type and set move cmd (Windows untested!)
$myos = substr(PHP_OS,0,3);
$myos = strtoupper($myos);
if ($myos==='WIN') $osmv = 'MOVE';
else $osmv = 'mv';
# Generate tmp dir name by hashing epoch time & random number
$tmpdirname = md5(time() . rand());
# Make temporary directory
if (@mkdir(LYCHEE_DATA . $tmpdirname)===false) {
Log::error($database, __METHOD__, __LINE__, 'Failed to create temporary directory');
return false;
}
# Get list of files and move them to tmpdir
$files = glob($path . '*');
if (isset($files)) {
foreach ($files as $file) {
# Prevent unsupported files from being moved
if (is_dir($file)===false&&@exif_imagetype($file)===false) continue;
$out = '';
$ret = '';
$file = escapeshellarg($file);
$cmd = $osmv . " $file " . LYCHEE_DATA . $tmpdirname;
@exec($cmd, $out, $ret);
if (isset($ret)&&($ret>0)) Log::error($database, __METHOD__, __LINE__, "Failed to move directory or file ($ret):" . $file);
}
}
# If no files could be copied to the temp dir, remove
$files = glob(LYCHEE_DATA . $tmpdirname . '/*');
if (count($files)===0) {
rmdir(LYCHEE_DATA . $tmpdirname);
Log::error($database, __METHOD__, __LINE__, 'Import failed, because files could not be temporary moved to ' . LYCHEE_DATA);
return false;
}
# Set new path
$path = LYCHEE_DATA . $tmpdirname;
return $path;
}
static function server($albumID = 0, $path, $useTemp = false) {
global $database, $plugins, $settings;
if (!isset($path)) $path = LYCHEE_UPLOADS_IMPORT;
if ($useTemp===true) {
$path = Import::move($database, $path);
if ($path===false) {
Log::error($database, __METHOD__, __LINE__, 'Failed to move import to temporary directory');
return false;
}
}
$error = false;
$contains['photos'] = false;
$contains['albums'] = false;
# Get all files
$files = glob($path . '/*');
foreach ($files as $file) {
# It is possible to move a file because of directory permissions but
# the file may still be unreadable by the user
if (!is_readable($file)) {
$error = true;
Log::error($database, __METHOD__, __LINE__, 'Could not read file or directory: ' . $file);
continue;
}
if (@exif_imagetype($file)!==false) {
# Photo
if (!Import::photo($database, $plugins, $settings, $file, $albumID)) {
$error = true;
Log::error($database, __METHOD__, __LINE__, 'Could not import file: ' . $file);
continue;
}
$contains['photos'] = true;
} else if (is_dir($file)) {
# Folder
$name = mysqli_real_escape_string($database, basename($file));
$album = new Album($database, null, null, null);
$newAlbumID = $album->add('[Import] ' . $name);
if ($newAlbumID===false) {
$error = true;
Log::error($database, __METHOD__, __LINE__, 'Could not create album in Lychee (' . $newAlbumID . ')');
continue;
}
if (Import::server($newAlbumID, $file . '/', false)==='Warning: Folder empty or no readable files to process!') {
$error = true;
Log::error($database, __METHOD__, __LINE__, 'Could not import folder. Function returned error');
continue;
}
$contains['albums'] = true;
}
}
# Delete tmpdir if import was successful
if ($error===false&&$useTemp===true&&file_exists(LYCHEE_DATA . $tmpdirname)) {
if (@rmdir(LYCHEE_DATA . $tmpdirname)===false) Log::error($database, __METHOD__, __LINE__, 'Could not delete temp-folder (' . LYCHEE_DATA . $tmpdirname . ') after successful import');
}
if ($contains['photos']===false&&$contains['albums']===false) return 'Warning: Folder empty or no readable files to process!';
if ($contains['photos']===false&&$contains['albums']===true) return 'Notice: Import only contains albums!';
return true;
}
}
?>

56
php/modules/Log.php Normal file
View File

@ -0,0 +1,56 @@
<?php
###
# @name Log Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Log extends Module {
public static function notice($database, $function, $line, $text = '') {
return Log::text($database, 'notice', $function, $line, $text);
}
public static function warning($database, $function, $line, $text = '') {
return Log::text($database, 'warning', $function, $line, $text);
}
public static function error($database, $function, $line, $text = '') {
return Log::text($database, 'error', $function, $line, $text);
}
public static function text($database, $type, $function, $line, $text = '') {
# Check dependencies
Module::dependencies(isset($database, $type, $function, $line, $text));
# Get time
$sysstamp = time();
# Escape
$type = mysqli_real_escape_string($database, $type);
$function = mysqli_real_escape_string($database, $function);
$line = mysqli_real_escape_string($database, $line);
$text = mysqli_real_escape_string($database, $text);
# Save in database
$query = "INSERT INTO lychee_log (time, type, function, line, text) VALUES ('$sysstamp', '$type', '$function', '$line', '$text');";
$result = $database->query($query);
if (!$result) return false;
return true;
}
}
?>

37
php/modules/Module.php Normal file
View File

@ -0,0 +1,37 @@
<?php
###
# @name Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Module {
protected $plugins = null;
protected function plugins($name, $location, $args) {
if (!isset($this->plugins, $name, $location, $args)) return false;
# Parse
$location = ($location===0 ? 'before' : 'after');
# Call plugins
$this->plugins->activate($name . ":" . $location, $args);
return true;
}
public function dependencies($available = false) {
if ($available===false) exit('Error: Can not execute function. Missing parameters or variables.');
}
}
?>

796
php/modules/Photo.php Executable file
View File

@ -0,0 +1,796 @@
<?php
###
# @name Photo Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Photo extends Module {
private $database = null;
private $settings = null;
private $photoIDs = null;
private $allowedTypes = array(
IMAGETYPE_JPEG,
IMAGETYPE_GIF,
IMAGETYPE_PNG
);
private $validExtensions = array(
'.jpg',
'.jpeg',
'.png',
'.gif'
);
public function __construct($database, $plugins, $settings, $photoIDs) {
# Init vars
$this->database = $database;
$this->plugins = $plugins;
$this->settings = $settings;
$this->photoIDs = $photoIDs;
return true;
}
public function add($files, $albumID, $description = '', $tags = '') {
# Check dependencies
$this->dependencies(isset($this->database));
# Check permissions
if (hasPermissions(LYCHEE_UPLOADS_BIG)===false||hasPermissions(LYCHEE_UPLOADS_THUMB)===false) {
Log::error($this->database, __METHOD__, __LINE__, 'Wrong permissions in uploads/');
exit('Error: Wrong permissions in uploads-folder!');
}
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
switch($albumID) {
case 's':
# s for public (share)
$public = 1;
$star = 0;
$albumID = 0;
break;
case 'f':
# f for starred (fav)
$star = 1;
$public = 0;
$albumID = 0;
break;
default:
$star = 0;
$public = 0;
break;
}
foreach ($files as $file) {
# Verify extension
$extension = getExtension($file['name']);
if (!in_array(strtolower($extension), $this->validExtensions, true)) continue;
# Verify image
$type = @exif_imagetype($file['tmp_name']);
if (!in_array($type, $this->allowedTypes, true)) continue;
# Generate id
$id = str_replace('.', '', microtime(true));
while(strlen($id)<14) $id .= 0;
$tmp_name = $file['tmp_name'];
$photo_name = md5($id) . $extension;
$path = LYCHEE_UPLOADS_BIG . $photo_name;
# Import if not uploaded via web
if (!is_uploaded_file($tmp_name)) {
if (!@copy($tmp_name, $path)) {
Log::error($this->database, __METHOD__, __LINE__, 'Could not copy photo to uploads');
exit('Error: Could not copy photo to uploads!');
} else @unlink($tmp_name);
} else {
if (!@move_uploaded_file($tmp_name, $path)) {
Log::error($this->database, __METHOD__, __LINE__, 'Could not move photo to uploads');
exit('Error: Could not move photo to uploads!');
}
}
# Read infos
$info = $this->getInfo($path);
# Use title of file if IPTC title missing
if ($info['title']==='') $info['title'] = mysqli_real_escape_string($this->database, substr(basename($file['name'], $extension), 0, 30));
# Use description parameter if set
if ($description==='') $description = $info['description'];
# Set orientation based on EXIF data
if ($file['type']==='image/jpeg'&&isset($info['orientation'])&&$info['orientation']!==''&&isset($info['width'])&&isset($info['height'])) {
if (!$this->adjustFile($path, $info)) Log::notice($this->database, __METHOD__, __LINE__, 'Could not adjust photo (' . $info['title'] . ')');
}
# Set original date
if ($info['takestamp']!=='') @touch($path, $info['takestamp']);
# Create Thumb
if (!$this->createThumb($path, $photo_name)) {
Log::error($this->database, __METHOD__, __LINE__, 'Could not create thumbnail for photo');
exit('Error: Could not create thumbnail for photo!');
}
# Save to DB
$query = "INSERT INTO lychee_photos (id, title, url, description, tags, type, width, height, size, iso, aperture, make, model, shutter, focal, takestamp, thumbUrl, album, public, star)
VALUES (
'" . $id . "',
'" . $info['title'] . "',
'" . $photo_name . "',
'" . $description . "',
'" . $tags . "',
'" . $info['type'] . "',
'" . $info['width'] . "',
'" . $info['height'] . "',
'" . $info['size'] . "',
'" . $info['iso'] . "',
'" . $info['aperture'] . "',
'" . $info['make'] . "',
'" . $info['model'] . "',
'" . $info['shutter'] . "',
'" . $info['focal'] . "',
'" . $info['takestamp'] . "',
'" . md5($id) . ".jpeg',
'" . $albumID . "',
'" . $public . "',
'" . $star . "');";
$result = $this->database->query($query);
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
exit('Error: Could not save photo in database!');
}
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return true;
}
private function createThumb($url, $filename, $width = 200, $height = 200) {
# Check dependencies
$this->dependencies(isset($this->database, $this->settings, $url, $filename));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
$info = getimagesize($url);
$photoName = explode(".", $filename);
$newUrl = LYCHEE_UPLOADS_THUMB . $photoName[0] . '.jpeg';
$newUrl2x = LYCHEE_UPLOADS_THUMB . $photoName[0] . '@2x.jpeg';
# create thumbnails with Imagick
if(extension_loaded('imagick')) {
# Read image
$thumb = new Imagick();
$thumb->readImage($url);
$thumb->setImageCompressionQuality($this->settings['thumbQuality']);
$thumb->setImageFormat('jpeg');
# Copy image for 2nd thumb version
$thumb2x = clone $thumb;
# Create 1st version
$thumb->cropThumbnailImage($width, $height);
$thumb->writeImage($newUrl);
$thumb->clear();
$thumb->destroy();
# Create 2nd version
$thumb2x->cropThumbnailImage($width*2, $height*2);
$thumb2x->writeImage($newUrl2x);
$thumb2x->clear();
$thumb2x->destroy();
} else {
# Set position and size
$thumb = imagecreatetruecolor($width, $height);
$thumb2x = imagecreatetruecolor($width*2, $height*2);
if ($info[0]<$info[1]) {
$newSize = $info[0];
$startWidth = 0;
$startHeight = $info[1]/2 - $info[0]/2;
} else {
$newSize = $info[1];
$startWidth = $info[0]/2 - $info[1]/2;
$startHeight = 0;
}
# Create new image
switch($info['mime']) {
case 'image/jpeg': $sourceImg = imagecreatefromjpeg($url); break;
case 'image/png': $sourceImg = imagecreatefrompng($url); break;
case 'image/gif': $sourceImg = imagecreatefromgif($url); break;
default: Log::error($this->database, __METHOD__, __LINE__, 'Type of photo is not supported');
return false;
break;
}
# Create thumb
fastimagecopyresampled($thumb, $sourceImg, 0, 0, $startWidth, $startHeight, $width, $height, $newSize, $newSize);
imagejpeg($thumb, $newUrl, $this->settings['thumbQuality']);
imagedestroy($thumb);
# Create retina thumb
fastimagecopyresampled($thumb2x, $sourceImg, 0, 0, $startWidth, $startHeight, $width*2, $height*2, $newSize, $newSize);
imagejpeg($thumb2x, $newUrl2x, $this->settings['thumbQuality']);
imagedestroy($thumb2x);
# Free memory
imagedestroy($sourceImg);
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return true;
}
private function adjustFile($path, $info) {
# Check dependencies
$this->dependencies(isset($path, $info));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
if (extension_loaded('imagick')) {
$rotateImage = 0;
switch ($info['orientation']) {
case 3:
$rotateImage = 180;
$imageOrientation = 1;
break;
case 6:
$rotateImage = 90;
$imageOrientation = 1;
break;
case 8:
$rotateImage = 270;
$imageOrientation = 1;
break;
}
if ($rotateImage!==0) {
$image = new Imagick();
$image->readImage($path);
$image->rotateImage(new ImagickPixel(), $rotateImage);
$image->setImageOrientation($imageOrientation);
$image->writeImage($path);
$image->clear();
$image->destroy();
}
} else {
$newWidth = $info['width'];
$newHeight = $info['height'];
$process = false;
$sourceImg = imagecreatefromjpeg($path);
switch ($info['orientation']) {
case 2:
# mirror
# not yet implemented
break;
case 3:
$process = true;
$sourceImg = imagerotate($sourceImg, -180, 0);
break;
case 4:
# rotate 180 and mirror
# not yet implemented
break;
case 5:
# rotate 90 and mirror
# not yet implemented
break;
case 6:
$process = true;
$sourceImg = imagerotate($sourceImg, -90, 0);
$newWidth = $info['height'];
$newHeight = $info['width'];
break;
case 7:
# rotate -90 and mirror
# not yet implemented
break;
case 8:
$process = true;
$sourceImg = imagerotate($sourceImg, 90, 0);
$newWidth = $info['height'];
$newHeight = $info['width'];
break;
}
# Need to adjust photo?
if ($process===true) {
# Recreate photo
$newSourceImg = imagecreatetruecolor($newWidth, $newHeight);
imagecopyresampled($newSourceImg, $sourceImg, 0, 0, 0, 0, $newWidth, $newHeight, $newWidth, $newHeight);
imagejpeg($newSourceImg, $path, 100);
# Free memory
imagedestroy($sourceImg);
imagedestroy($newSourceImg);
}
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return true;
}
public function get($albumID) {
# Check dependencies
$this->dependencies(isset($this->database, $this->photoIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Get photo
$photos = $this->database->query("SELECT * FROM lychee_photos WHERE id = '$this->photoIDs' LIMIT 1;");
$photo = $photos->fetch_assoc();
# Parse photo
$photo['sysdate'] = date('d M. Y', substr($photo['id'], 0, -4));
if (strlen($photo['takestamp'])>1) $photo['takedate'] = date('d M. Y', $photo['takestamp']);
if ($albumID!='false') {
if ($photo['album']!=0) {
# Get album
$albums = $this->database->query("SELECT public FROM lychee_albums WHERE id = '" . $photo['album'] . " LIMIT 1';");
$album = $albums->fetch_assoc();
# Parse album
$photo['public'] = ($album['public']=='1' ? '2' : $photo['public']);
}
$photo['original_album'] = $photo['album'];
$photo['album'] = $albumID;
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return $photo;
}
private function getInfo($url) {
# Check dependencies
$this->dependencies(isset($this->database, $url));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
$iptcArray = array();
$info = getimagesize($url, $iptcArray);
# General information
$return['type'] = $info['mime'];
$return['width'] = $info[0];
$return['height'] = $info[1];
# Size
$size = filesize($url)/1024;
if ($size>=1024) $return['size'] = round($size/1024, 1) . ' MB';
else $return['size'] = round($size, 1) . ' KB';
# IPTC Metadata Fallback
$return['title'] = '';
$return['description'] = '';
# IPTC Metadata
if(isset($iptcArray['APP13'])) {
$iptcInfo = iptcparse($iptcArray['APP13']);
if (is_array($iptcInfo)) {
$temp = @$iptcInfo['2#105'][0];
if (isset($temp)&&strlen($temp)>0) $return['title'] = $temp;
$temp = @$iptcInfo['2#120'][0];
if (isset($temp)&&strlen($temp)>0) $return['description'] = $temp;
}
}
# EXIF Metadata Fallback
$return['orientation'] = '';
$return['iso'] = '';
$return['aperture'] = '';
$return['make'] = '';
$return['model'] = '';
$return['shutter'] = '';
$return['focal'] = '';
$return['takestamp'] = 0;
# Read EXIF
if ($info['mime']=='image/jpeg') $exif = @exif_read_data($url, 'EXIF', 0);
else $exif = false;
# EXIF Metadata
if ($exif!==false) {
if (isset($exif['Orientation'])) $return['orientation'] = $exif['Orientation'];
else if (isset($exif['IFD0']['Orientation'])) $return['orientation'] = $exif['IFD0']['Orientation'];
$temp = @$exif['ISOSpeedRatings'];
if (isset($temp)) $return['iso'] = $temp;
$temp = @$exif['COMPUTED']['ApertureFNumber'];
if (isset($temp)) $return['aperture'] = $temp;
$temp = @$exif['Make'];
if (isset($temp)) $return['make'] = trim($temp);
$temp = @$exif['Model'];
if (isset($temp)) $return['model'] = trim($temp);
$temp = @$exif['ExposureTime'];
if (isset($temp)) $return['shutter'] = $exif['ExposureTime'] . ' Sec.';
$temp = @$exif['FocalLength'];
if (isset($temp)) $return['focal'] = ($temp/1) . ' mm';
$temp = @$exif['DateTimeOriginal'];
if (isset($temp)) $return['takestamp'] = strtotime($temp);
}
# Security
foreach(array_keys($return) as $key) $return[$key] = mysqli_real_escape_string($this->database, $return[$key]);
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return $return;
}
public function getArchive() {
# Check dependencies
$this->dependencies(isset($this->database, $this->photoIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Get photo
$photos = $this->database->query("SELECT title, url FROM lychee_photos WHERE id = '$this->photoIDs' LIMIT 1;");
$photo = $photos->fetch_object();
# Get extension
$extension = getExtension($photo->url);
if ($extension===false) {
Log::error($this->database, __METHOD__, __LINE__, 'Invalid photo extension');
return false;
}
# Parse title
if ($photo->title=='') $photo->title = 'Untitled';
# Set headers
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"" . $photo->title . $extension . "\"");
header("Content-Length: " . filesize(LYCHEE_UPLOADS_BIG . $photo->url));
# Send file
readfile(LYCHEE_UPLOADS_BIG . $photo->url);
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return true;
}
public function setTitle($title) {
# Check dependencies
$this->dependencies(isset($this->database, $this->photoIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Parse
if (strlen($title)>50) $title = substr($title, 0, 50);
# Set title
$result = $this->database->query("UPDATE lychee_photos SET title = '$title' WHERE id IN ($this->photoIDs);");
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
public function setDescription($description) {
# Check dependencies
$this->dependencies(isset($this->database, $this->photoIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Parse
$description = htmlentities($description);
if (strlen($description)>1000) $description = substr($description, 0, 1000);
# Set description
$result = $this->database->query("UPDATE lychee_photos SET description = '$description' WHERE id IN ('$this->photoIDs');");
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
public function setStar() {
# Check dependencies
$this->dependencies(isset($this->database, $this->photoIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Init vars
$error = false;
# Get photos
$photos = $this->database->query("SELECT id, star FROM lychee_photos WHERE id IN ($this->photoIDs);");
# For each photo
while ($photo = $photos->fetch_object()) {
# Invert star
$star = ($photo->star==0 ? 1 : 0);
# Set star
$star = $this->database->query("UPDATE lychee_photos SET star = '$star' WHERE id = '$photo->id';");
if (!$star) $error = true;
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if ($error===true) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
public function getPublic($password) {
# Check dependencies
$this->dependencies(isset($this->database, $this->photoIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Get photo
$photos = $this->database->query("SELECT public, album FROM lychee_photos WHERE id = '$this->photoIDs' LIMIT 1;");
$photo = $photos->fetch_object();
# Check if public
if ($photo->public==1) return true;
else {
$album = new Album($this->database, null, null, $photo->album);
$acP = $album->checkPassword($password);
$agP = $album->getPublic();
if ($acP===true&&$agP===true) return true;
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return false;
}
public function setPublic() {
# Check dependencies
$this->dependencies(isset($this->database, $this->photoIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Get public
$photos = $this->database->query("SELECT public FROM lychee_photos WHERE id = '$this->photoIDs' LIMIT 1;");
$photo = $photos->fetch_object();
# Invert public
$public = ($photo->public==0 ? 1 : 0);
# Set public
$result = $this->database->query("UPDATE lychee_photos SET public = '$public' WHERE id = '$this->photoIDs';");
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
function setAlbum($albumID) {
# Check dependencies
$this->dependencies(isset($this->database, $this->photoIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Set album
$result = $this->database->query("UPDATE lychee_photos SET album = '$albumID' WHERE id IN ($this->photoIDs);");
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
public function setTags($tags) {
# Check dependencies
$this->dependencies(isset($this->database, $this->photoIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Parse tags
$tags = preg_replace('/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/', ',', $tags);
$tags = preg_replace('/,$|^,|(\ ){0,}$/', '', $tags);
if (strlen($tags)>1000) {
Log::notice($this->database, __METHOD__, __LINE__, 'Length of tags higher than 1000');
return false;
}
# Set tags
$result = $this->database->query("UPDATE lychee_photos SET tags = '$tags' WHERE id IN ($this->photoIDs);");
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
public function delete() {
# Check dependencies
$this->dependencies(isset($this->database, $this->photoIDs));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Get photos
$photos = $this->database->query("SELECT id, url, thumbUrl FROM lychee_photos WHERE id IN ($this->photoIDs);");
if (!$photos) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
# For each photo
while ($photo = $photos->fetch_object()) {
# Get retina thumb url
$thumbUrl2x = explode(".", $photo->thumbUrl);
$thumbUrl2x = $thumbUrl2x[0] . '@2x.' . $thumbUrl2x[1];
# Delete big
if (file_exists(LYCHEE_UPLOADS_BIG . $photo->url)&&!unlink(LYCHEE_UPLOADS_BIG . $photo->url)) {
Log::error($this->database, __METHOD__, __LINE__, 'Could not delete photo in uploads/big/');
return false;
}
# Delete thumb
if (file_exists(LYCHEE_UPLOADS_THUMB . $photo->thumbUrl)&&!unlink(LYCHEE_UPLOADS_THUMB . $photo->thumbUrl)) {
Log::error($this->database, __METHOD__, __LINE__, 'Could not delete photo in uploads/thumb/');
return false;
}
# Delete thumb@2x
if (file_exists(LYCHEE_UPLOADS_THUMB . $thumbUrl2x)&&!unlink(LYCHEE_UPLOADS_THUMB . $thumbUrl2x)) {
Log::error($this->database, __METHOD__, __LINE__, 'Could not delete high-res photo in uploads/thumb/');
return false;
}
# Delete db entry
$delete = $this->database->query("DELETE FROM lychee_photos WHERE id = '$photo->id';");
if (!$delete) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return true;
}
}
?>

96
php/modules/Plugins.php Normal file
View File

@ -0,0 +1,96 @@
<?php
###
# @name Plugins Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Plugins implements \SplSubject {
private $files = array();
private $observers = array();
public $action = null;
public $args = null;
public function __construct($files, $database, $settings) {
if (!isset($files)) return false;
# Init vars
$plugins = $this;
$this->files = $files;
# Load plugins
foreach ($this->files as $file) {
if ($file==='') continue;
$file = LYCHEE_PLUGINS . $file;
if (file_exists($file)===false) {
Log::warning($database, __METHOD__, __LINE__, 'Could not include plugin. File does not exist (' . $file . ').');
continue;
}
include($file);
}
return true;
}
public function attach(\SplObserver $observer) {
if (!isset($observer)) return false;
# Add observer
$this->observers[] = $observer;
return true;
}
public function detach(\SplObserver $observer) {
if (!isset($observer)) return false;
# Remove observer
$key = array_search($observer, $this->observers, true);
if ($key) unset($this->observers[$key]);
return true;
}
public function notify() {
# Notify each observer
foreach ($this->observers as $value) $value->update($this);
return true;
}
public function activate($action, $args) {
if (!isset($action, $args)) return false;
# Save vars
$this->action = $action;
$this->args = $args;
# Notify observers
$this->notify();
return true;
}
}
?>

124
php/modules/Session.php Executable file
View File

@ -0,0 +1,124 @@
<?php
###
# @name Session Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Session extends Module {
private $settings = null;
public function __construct($plugins, $settings) {
# Init vars
$this->plugins = $plugins;
$this->settings = $settings;
return true;
}
public function init($database, $dbName, $public, $version) {
# Check dependencies
$this->dependencies(isset($this->settings, $public, $version));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Update
if (!isset($this->settings['version'])||$this->settings['version']!==$version) {
if (!Database::update($database, $dbName, @$this->settings['version'])) {
Log::error($database, __METHOD__, __LINE__, 'Updating the database failed');
exit('Error: Updating the database failed!');
}
}
# Return settings
$return['config'] = $this->settings;
unset($return['config']['password']);
# No login
if ($this->settings['username']===''&&$this->settings['password']==='') $return['config']['login'] = false;
else $return['config']['login'] = true;
if ($public===false) {
# Logged in
$return['loggedIn'] = true;
} else {
# Unset unused vars
unset($return['config']['username']);
unset($return['config']['thumbQuality']);
unset($return['config']['sorting']);
unset($return['config']['dropboxKey']);
unset($return['config']['login']);
# Logged out
$return['loggedIn'] = false;
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return $return;
}
public function login($username, $password) {
# Check dependencies
$this->dependencies(isset($this->settings, $username, $password));
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
# Check login with MD5 hash
if ($username===$this->settings['username']&&$password===$this->settings['password']) {
$_SESSION['login'] = true;
return true;
}
# Check login with crypted hash
if ($username===$this->settings['username']&&$this->settings['password']===crypt($password, $this->settings['password'])) {
$_SESSION['login'] = true;
return true;
}
# No login
if ($this->settings['username']===''&&$this->settings['password']==='') {
$_SESSION['login'] = true;
return true;
}
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return false;
}
public function logout() {
# Call plugins
$this->plugins(__METHOD__, 0, func_get_args());
session_destroy();
# Call plugins
$this->plugins(__METHOD__, 1, func_get_args());
return true;
}
}
?>

191
php/modules/Settings.php Executable file
View File

@ -0,0 +1,191 @@
<?php
###
# @name Settings Module
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
###
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
class Settings extends Module {
private $database = null;
public function __construct($database) {
# Init vars
$this->database = $database;
return true;
}
public function get() {
# Check dependencies
$this->dependencies(isset($this->database));
# Execute query
$settings = $this->database->query('SELECT * FROM lychee_settings;');
# Add each to return
while ($setting = $settings->fetch_object()) $return[$setting->key] = $setting->value;
# Fallback for versions below v2.5
if (!isset($return['plugins'])) $return['plugins'] = '';
return $return;
}
public function setLogin($oldPassword = '', $username, $password) {
# Check dependencies
$this->dependencies(isset($this->database));
# Load settings
$settings = $this->get();
if ($oldPassword===$settings['password']||$settings['password']===crypt($oldPassword, $settings['password'])) {
# Save username
if (!$this->setUsername($username)) exit('Error: Updating username failed!');
# Save password
if (!$this->setPassword($password)) exit('Error: Updating password failed!');
return true;
}
exit('Error: Current password entered incorrectly!');
}
private function setUsername($username) {
# Check dependencies
$this->dependencies(isset($this->database));
# Parse
$username = htmlentities($username);
if (strlen($username)>50) {
Log::notice($this->database, __METHOD__, __LINE__, 'Username is longer than 50 chars');
return false;
}
# Execute query
$result = $this->database->query("UPDATE lychee_settings SET value = '$username' WHERE `key` = 'username';");
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
private function setPassword($password) {
# Check dependencies
$this->dependencies(isset($this->database));
$password = get_hashed_password($password);
# Execute query
$result = $this->database->query("UPDATE lychee_settings SET value = '$password' WHERE `key` = 'password';");
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
public function setDropboxKey($key) {
# Check dependencies
$this->dependencies(isset($this->database, $key));
if (strlen($key)<1||strlen($key)>50) {
Log::notice($this->database, __METHOD__, __LINE__, 'Dropbox key is either too short or too long');
return false;
}
# Execute query
$result = $this->database->query("UPDATE lychee_settings SET value = '$key' WHERE `key` = 'dropboxKey';");
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
public function setSorting($type, $order) {
# Check dependencies
$this->dependencies(isset($this->database, $type, $order));
$sorting = 'ORDER BY ';
# Set row
switch ($type) {
case 'id': $sorting .= 'id';
break;
case 'title': $sorting .= 'title';
break;
case 'description': $sorting .= 'description';
break;
case 'public': $sorting .= 'public';
break;
case 'type': $sorting .= 'type';
break;
case 'star': $sorting .= 'star';
break;
case 'takestamp': $sorting .= 'takestamp';
break;
default: exit('Error: Unknown type for sorting!');
}
$sorting .= ' ';
# Set order
switch ($order) {
case 'ASC': $sorting .= 'ASC';
break;
case 'DESC': $sorting .= 'DESC';
break;
default: exit('Error: Unknown order for sorting!');
}
# Execute query
$result = $this->database->query("UPDATE lychee_settings SET value = '$sorting' WHERE `key` = 'sorting';");
if (!$result) {
Log::error($this->database, __METHOD__, __LINE__, $this->database->error);
return false;
}
return true;
}
}
?>

View File

@ -1,353 +0,0 @@
<?php
/**
* @name Album Module
* @author Philipp Maurer
* @author Tobias Reich
* @copyright 2014 by Philipp Maurer, Tobias Reich
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
function addAlbum($title, $public = 0) {
global $database;
if (strlen($title)<1||strlen($title)>50) return false;
$sysdate = date("d.m.Y");
$result = $database->query("INSERT INTO lychee_albums (title, sysdate, public) VALUES ('$title', '$sysdate', '$public');");
if (!$result) return false;
return $database->insert_id;
}
function getAlbums($public) {
global $database, $settings;
// Smart Albums
if (!$public) $return = getSmartInfo();
// Albums
if ($public) $query = "SELECT id, title, public, sysdate, password FROM lychee_albums WHERE public = 1";
else $query = "SELECT id, title, public, sysdate, password FROM lychee_albums";
$result = $database->query($query) OR exit("Error: $result <br>".$database->error);
$i = 0;
while($row = $result->fetch_object()) {
// Info
$return["content"][$row->id]['id'] = $row->id;
$return["content"][$row->id]['title'] = $row->title;
$return["content"][$row->id]['public'] = $row->public;
$return["content"][$row->id]['sysdate'] = date('F Y', strtotime($row->sysdate));
// Password
if ($row->password=="") $return["content"][$row->id]['password'] = false;
else $return["content"][$row->id]['password'] = true;
// Thumbs
if (($public&&$row->password=="")||(!$public)) {
$albumID = $row->id;
$result2 = $database->query("SELECT thumbUrl FROM lychee_photos WHERE album = '$albumID' ORDER BY star DESC, " . substr($settings['sorting'], 9) . " LIMIT 0, 3");
$k = 0;
while($row2 = $result2->fetch_object()){
$return["content"][$row->id]["thumb$k"] = $row2->thumbUrl;
$k++;
}
if (!isset($return["content"][$row->id]["thumb0"])) $return["content"][$row->id]["thumb0"] = "";
if (!isset($return["content"][$row->id]["thumb1"])) $return["content"][$row->id]["thumb1"] = "";
if (!isset($return["content"][$row->id]["thumb2"])) $return["content"][$row->id]["thumb2"] = "";
}
// Album count
$i++;
}
$return["num"] = $i;
return $return;
}
function getSmartInfo() {
global $database, $settings;
// Unsorted
$result = $database->query("SELECT thumbUrl FROM lychee_photos WHERE album = 0 " . $settings['sorting']);
$i = 0;
while($row = $result->fetch_object()) {
if ($i<3) $return["unsortedThumb$i"] = $row->thumbUrl;
$i++;
}
$return['unsortedNum'] = $i;
// Public
$result2 = $database->query("SELECT thumbUrl FROM lychee_photos WHERE public = 1 " . $settings['sorting']);
$i = 0;
while($row2 = $result2->fetch_object()) {
if ($i<3) $return["publicThumb$i"] = $row2->thumbUrl;
$i++;
}
$return['publicNum'] = $i;
// Starred
$result3 = $database->query("SELECT thumbUrl FROM lychee_photos WHERE star = 1 " . $settings['sorting']);
$i = 0;
while($row3 = $result3->fetch_object()) {
if ($i<3) $return["starredThumb$i"] = $row3->thumbUrl;
$i++;
}
$return['starredNum'] = $i;
return $return;
}
function getAlbum($albumID) {
global $database, $settings;
// Get album information
switch($albumID) {
case "f": $return['public'] = false;
$query = "SELECT id, title, tags, sysdate, public, star, album, thumbUrl FROM lychee_photos WHERE star = 1 " . $settings['sorting'];
break;
case "s": $return['public'] = false;
$query = "SELECT id, title, tags, sysdate, public, star, album, thumbUrl FROM lychee_photos WHERE public = 1 " . $settings['sorting'];
break;
case "0": $return['public'] = false;
$query = "SELECT id, title, tags, sysdate, public, star, album, thumbUrl FROM lychee_photos WHERE album = 0 " . $settings['sorting'];
break;
default: $result = $database->query("SELECT * FROM lychee_albums WHERE id = '$albumID';");
$row = $result->fetch_object();
$return['title'] = $row->title;
$return['description'] = $row->description;
$return['sysdate'] = date('d M. Y', strtotime($row->sysdate));
$return['public'] = $row->public;
$return['password'] = ($row->password=="" ? false : true);
$query = "SELECT id, title, tags, sysdate, public, star, album, thumbUrl FROM lychee_photos WHERE album = '$albumID' " . $settings['sorting'];
break;
}
// Get photos
$result = $database->query($query);
$previousPhotoID = "";
$i = 0;
while($row = $result->fetch_assoc()) {
$return['content'][$row['id']]['id'] = $row['id'];
$return['content'][$row['id']]['title'] = $row['title'];
$return['content'][$row['id']]['sysdate'] = date('d F Y', strtotime($row['sysdate']));
$return['content'][$row['id']]['public'] = $row['public'];
$return['content'][$row['id']]['star'] = $row['star'];
$return['content'][$row['id']]['tags'] = $row['tags'];
$return['content'][$row['id']]['album'] = $row['album'];
$return['content'][$row['id']]['thumbUrl'] = $row['thumbUrl'];
$return['content'][$row['id']]['previousPhoto'] = $previousPhotoID;
$return['content'][$row['id']]['nextPhoto'] = "";
if ($previousPhotoID!="") $return['content'][$previousPhotoID]['nextPhoto'] = $row['id'];
$previousPhotoID = $row['id'];
$i++;
}
if ($i==0) {
// Empty album
$return['content'] = false;
} else {
// Enable next and previous for the first and last photo
$lastElement = end($return['content']);
$lastElementId = $lastElement['id'];
$firstElement = reset($return['content']);
$firstElementId = $firstElement['id'];
if ($lastElementId!==$firstElementId) {
$return['content'][$lastElementId]['nextPhoto'] = $firstElementId;
$return['content'][$firstElementId]['previousPhoto'] = $lastElementId;
}
}
$return['id'] = $albumID;
$return['num'] = $i;
return $return;
}
function setAlbumTitle($albumIDs, $title) {
global $database;
if (strlen($title)<1||strlen($title)>50) return false;
$result = $database->query("UPDATE lychee_albums SET title = '$title' WHERE id IN ($albumIDs);");
if (!$result) return false;
return true;
}
function setAlbumDescription($albumID, $description) {
global $database;
$description = htmlentities($description);
if (strlen($description)>1000) return false;
$result = $database->query("UPDATE lychee_albums SET description = '$description' WHERE id = '$albumID';");
if (!$result) return false;
return true;
}
function deleteAlbum($albumIDs) {
global $database;
$error = false;
$result = $database->query("SELECT id FROM lychee_photos WHERE album IN ($albumIDs);");
// Delete photos
while ($row = $result->fetch_object())
if (!deletePhoto($row->id)) $error = true;
// Delete album
$result = $database->query("DELETE FROM lychee_albums WHERE id IN ($albumIDs);");
if ($error||!$result) return false;
return true;
}
function getAlbumArchive($albumID) {
global $database;
switch($albumID) {
case 's':
$query = "SELECT url FROM lychee_photos WHERE public = '1';";
$zipTitle = "Public";
break;
case 'f':
$query = "SELECT url FROM lychee_photos WHERE star = '1';";
$zipTitle = "Starred";
break;
default:
$query = "SELECT url FROM lychee_photos WHERE album = '$albumID';";
$zipTitle = "Unsorted";
}
$zip = new ZipArchive();
$result = $database->query($query);
$files = array();
$i = 0;
while($row = $result->fetch_object()) {
$files[$i] = "../uploads/big/".$row->url;
$i++;
}
$result = $database->query("SELECT title FROM lychee_albums WHERE id = '$albumID' LIMIT 1;");
$row = $result->fetch_object();
if ($albumID!=0&&is_numeric($albumID)) $zipTitle = $row->title;
$filename = "../data/$zipTitle.zip";
if ($zip->open($filename, ZIPARCHIVE::CREATE)!==TRUE) {
return false;
}
foreach($files AS $zipFile) {
$newFile = explode("/",$zipFile);
$newFile = array_reverse($newFile);
$zip->addFile($zipFile, $zipTitle."/".$newFile[0]);
}
$zip->close();
header("Content-Type: application/zip");
header("Content-Disposition: attachment; filename=\"$zipTitle.zip\"");
header("Content-Length: ".filesize($filename));
readfile($filename);
unlink($filename);
return true;
}
function setAlbumPublic($albumID, $password) {
global $database;
$result = $database->query("SELECT public FROM lychee_albums WHERE id = '$albumID';");
$row = $result->fetch_object();
$public = ($row->public=='0' ? 1 : 0);
$result = $database->query("UPDATE lychee_albums SET public = '$public', password = NULL WHERE id = '$albumID';");
if (!$result) return false;
if ($public==1) {
$result = $database->query("UPDATE lychee_photos SET public = 0 WHERE album = '$albumID';");
if (!$result) return false;
}
if (strlen($password)>0) return setAlbumPassword($albumID, $password);
return true;
}
function setAlbumPassword($albumID, $password) {
global $database;
$result = $database->query("UPDATE lychee_albums SET password = '$password' WHERE id = '$albumID';");
if (!$result) return false;
return true;
}
function checkAlbumPassword($albumID, $password) {
global $database;
$result = $database->query("SELECT password FROM lychee_albums WHERE id = '$albumID';");
$row = $result->fetch_object();
if ($row->password=="") return true;
else if ($row->password==$password) return true;
return false;
}
function isAlbumPublic($albumID) {
global $database;
$result = $database->query("SELECT public FROM lychee_albums WHERE id = '$albumID';");
$row = $result->fetch_object();
if ($albumID==='0'||$albumID==='s'||$albumID==='f') return false;
if ($row->public==1) return true;
return false;
}
?>

View File

@ -1,185 +0,0 @@
<?php
/**
* @name DB Module
* @author Philipp Maurer
* @author Tobias Reich
* @copyright 2014 by Philipp Maurer, Tobias Reich
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
function dbConnect() {
global $dbUser, $dbPassword, $dbHost, $dbName;
$database = new mysqli($dbHost, $dbUser, $dbPassword);
if ($database->connect_errno) exit('Error: ' . $database->connect_error);
// Avoid sql injection on older MySQL versions
if ($database->server_version<50500) $database->set_charset('GBK');
if (!$database->select_db($dbName))
if (!dbCreate($dbName, $database)) exit('Error: Could not create database!');
if (!$database->query('SELECT * FROM lychee_photos, lychee_albums, lychee_settings LIMIT 0;'))
if (!dbCreateTables($database)) exit('Error: Could not create tables!');
return $database;
}
function dbCreateConfig($dbHost = 'localhost', $dbUser, $dbPassword, $dbName = 'lychee', $version) {
$dbPassword = urldecode($dbPassword);
$database = new mysqli($dbHost, $dbUser, $dbPassword);
if ($database->connect_errno) return 'Warning: Connection failed!';
else {
$config = "<?php
/**
* @name Config
* @author Tobias Reich
* @copyright 2014 Tobias Reich
*/
if(!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
// Config version
\$configVersion = '';
// Database configuration
\$dbHost = '$dbHost'; //Host of the Database
\$dbUser = '$dbUser'; //Username of the database
\$dbPassword = '$dbPassword'; //Password of the Database
\$dbName = '$dbName'; //Database name
?>";
if (file_put_contents('../data/config.php', $config)===false) return 'Warning: Could not create file!';
$_SESSION['login'] = true;
return true;
}
}
function dbCreate($dbName, $database) {
$result = $database->query("CREATE DATABASE IF NOT EXISTS $dbName;");
$database->select_db($dbName);
if (!$database->select_db($dbName)||!$result) return false;
return true;
}
function dbCreateTables($database) {
if (!$database->query('SELECT * FROM lychee_settings LIMIT 0;')) {
$query = "
CREATE TABLE `lychee_settings` (
`key` varchar(50) NOT NULL DEFAULT '',
`value` varchar(50) DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
";
if (!$database->query($query)) return false;
$query = "
INSERT INTO `lychee_settings` (`key`, `value`)
VALUES
('username',''),
('password',''),
('thumbQuality','90'),
('checkForUpdates','1'),
('sorting','ORDER BY id DESC'),
('dropboxKey','');
";
if (!$database->query($query)) return false;
}
if (!$database->query('SELECT * FROM lychee_albums LIMIT 0;')) {
$query = "
CREATE TABLE `lychee_albums` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(50) NOT NULL,
`description` varchar(1000) DEFAULT '',
`sysdate` varchar(10) NOT NULL,
`public` tinyint(1) DEFAULT '0',
`password` varchar(100) DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
";
if (!$database->query($query)) return false;
}
if (!$database->query('SELECT * FROM lychee_photos LIMIT 0;')) {
$query = "
CREATE TABLE `lychee_photos` (
`id` bigint(14) NOT NULL,
`title` varchar(50) NOT NULL,
`description` varchar(1000) NOT NULL DEFAULT '',
`url` varchar(100) NOT NULL,
`tags` varchar(1000) NOT NULL DEFAULT '',
`public` tinyint(1) NOT NULL,
`type` varchar(10) NOT NULL,
`width` int(11) NOT NULL,
`height` int(11) NOT NULL,
`size` varchar(20) NOT NULL,
`sysdate` varchar(10) NOT NULL,
`systime` varchar(8) NOT NULL,
`iso` varchar(15) NOT NULL,
`aperture` varchar(20) NOT NULL,
`make` varchar(20) NOT NULL,
`model` varchar(50) NOT NULL,
`shutter` varchar(30) NOT NULL,
`focal` varchar(20) NOT NULL,
`takedate` varchar(20) NOT NULL,
`taketime` varchar(8) NOT NULL,
`star` tinyint(1) NOT NULL,
`thumbUrl` varchar(50) NOT NULL,
`album` varchar(30) NOT NULL DEFAULT '0',
`import_name` varchar(100) DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
";
if (!$database->query($query)) return false;
}
return true;
}
function dbClose() {
global $database;
if (!$database->close()) exit('Error: Closing the connection failed!');
return true;
}
?>

View File

@ -9,9 +9,50 @@
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
function openGraphHeader($photoID) {
function search($database, $settings, $term) {
global $database;
if (!isset($database, $settings, $term)) return false;
$return['albums'] = '';
// Photos
$result = $database->query("SELECT id, title, tags, public, star, album, thumbUrl FROM lychee_photos WHERE title like '%$term%' OR description like '%$term%' OR tags like '%$term%';");
while($row = $result->fetch_assoc()) {
$return['photos'][$row['id']] = $row;
$return['photos'][$row['id']]['sysdate'] = date('d M. Y', substr($row['id'], 0, -4));
}
// Albums
$result = $database->query("SELECT id, title, public, sysstamp, password FROM lychee_albums WHERE title like '%$term%' OR description like '%$term%';");
$i = 0;
while($row = $result->fetch_object()) {
// Info
$return['albums'][$row->id]['id'] = $row->id;
$return['albums'][$row->id]['title'] = $row->title;
$return['albums'][$row->id]['public'] = $row->public;
$return['albums'][$row->id]['sysdate'] = date('F Y', $row->sysstamp);
$return['albums'][$row->id]['password'] = ($row->password=='' ? false : true);
// Thumbs
$result2 = $database->query("SELECT thumbUrl FROM lychee_photos WHERE album = '" . $row->id . "' " . $settings['sorting'] . " LIMIT 0, 3;");
$k = 0;
while($row2 = $result2->fetch_object()){
$return['albums'][$row->id]["thumb$k"] = $row2->thumbUrl;
$k++;
}
$i++;
}
return $return;
}
function getGraphHeader($database, $photoID) {
if (!isset($database, $photoID)) return false;
$photoID = mysqli_real_escape_string($database, $photoID);
@ -39,74 +80,81 @@ function openGraphHeader($photoID) {
}
function search($term) {
function getExtension($filename) {
global $database, $settings;
$extension = strpos($filename, '.') !== false
? strrchr($filename, '.')
: '';
$return['albums'] = '';
// Photos
$result = $database->query("SELECT id, title, tags, sysdate, public, star, album, thumbUrl FROM lychee_photos WHERE title like '%$term%' OR description like '%$term%' OR tags like '%$term%';");
while($row = $result->fetch_assoc()) {
$return['photos'][$row['id']] = $row;
$return['photos'][$row['id']]['sysdate'] = date('d F Y', strtotime($row['sysdate']));
}
// Albums
$result = $database->query("SELECT id, title, public, sysdate, password FROM lychee_albums WHERE title like '%$term%' OR description like '%$term%';");
$i = 0;
while($row = $result->fetch_object()) {
// Info
$return['albums'][$row->id]['id'] = $row->id;
$return['albums'][$row->id]['title'] = $row->title;
$return['albums'][$row->id]['public'] = $row->public;
$return['albums'][$row->id]['sysdate'] = date('F Y', strtotime($row->sysdate));
$return['albums'][$row->id]['password'] = ($row->password=='' ? false : true);
// Thumbs
$result2 = $database->query("SELECT thumbUrl FROM lychee_photos WHERE album = '" . $row->id . "' " . $settings['sorting'] . " LIMIT 0, 3;");
$k = 0;
while($row2 = $result2->fetch_object()){
$return['albums'][$row->id]["thumb$k"] = $row2->thumbUrl;
$k++;
}
$i++;
}
return $return;
return $extension;
}
function update($version = '') {
function get_hashed_password($password) {
global $database, $configVersion;
# Inspired by http://alias.io/2010/01/store-passwords-safely-with-php-and-mysql/
// Albums
if(!$database->query("SELECT `description` FROM `lychee_albums` LIMIT 1;")) $database->query("ALTER TABLE `lychee_albums` ADD `description` VARCHAR( 1000 ) NULL DEFAULT ''"); // v2.0
if($database->query("SELECT `password` FROM `lychee_albums` LIMIT 1;")) $database->query("ALTER TABLE `lychee_albums` CHANGE `password` `password` VARCHAR( 100 ) NULL DEFAULT ''"); // v2.0
# A higher $cost is more secure but consumes more processing power
$cost = 10;
// Photos
if($database->query("SELECT `description` FROM `lychee_photos` LIMIT 1;")) $database->query("ALTER TABLE `lychee_photos` CHANGE `description` `description` VARCHAR( 1000 ) NULL DEFAULT ''"); // v2.0
if(!$database->query("SELECT `tags` FROM `lychee_photos` LIMIT 1;")) $database->query("ALTER TABLE `lychee_photos` ADD `tags` VARCHAR( 1000 ) NULL DEFAULT ''"); // v2.1
$database->query("UPDATE `lychee_photos` SET url = replace(url, 'uploads/big/', ''), thumbUrl = replace(thumbUrl, 'uploads/thumb/', '')");
// Settings
$database->query("ALTER TABLE `lychee_settings` CHANGE `value` `value` VARCHAR( 200 ) NULL DEFAULT ''"); // v2.1.1
$result = $database->query("SELECT `key` FROM `lychee_settings` WHERE `key` = 'dropboxKey' LIMIT 1;");
if ($result->num_rows===0) $database->query("INSERT INTO `lychee_settings` (`key`, `value`) VALUES ('dropboxKey', '')"); // v2.1
// Config
if ($version!==''&&$configVersion!==$version) {
$data = file_get_contents('../data/config.php');
$data = preg_replace('/\$configVersion = \'[\w. ]*\';/', "\$configVersion = '$version';", $data);
if (file_put_contents('../data/config.php', $data)===false) return 'Error: Could not save updated config!';
# Create a random salt
if (extension_loaded('openssl')) {
$salt = strtr(substr(base64_encode(openssl_random_pseudo_bytes(17)),0,22), '+', '.');
} elseif (extension_loaded('mcrypt')) {
$salt = strtr(substr(base64_encode(mcrypt_create_iv(17, MCRYPT_DEV_URANDOM)),0,22), '+', '.');
} else {
$salt = "";
for ($i = 0; $i < 22; $i++) {
$salt .= substr("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", mt_rand(0, 63), 1);
}
}
# Prefix information about the hash so PHP knows how to verify it later.
# "$2a$" Means we're using the Blowfish algorithm. The following two digits are the cost parameter.
$salt = sprintf("$2a$%02d$", $cost) . $salt;
# Hash the password with the salt
return crypt($password, $salt);
}
function hasPermissions($path, $permissions = '0777') {
if (substr(sprintf('%o', @fileperms($path)), -4)!=$permissions) return false;
else return true;
}
function fastimagecopyresampled(&$dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h, $quality = 4) {
###
# Plug-and-Play fastimagecopyresampled function replaces much slower imagecopyresampled.
# Just include this function and change all "imagecopyresampled" references to "fastimagecopyresampled".
# Typically from 30 to 60 times faster when reducing high resolution images down to thumbnail size using the default quality setting.
# Author: Tim Eckel - Date: 09/07/07 - Version: 1.1 - Project: FreeRingers.net - Freely distributable - These comments must remain.
#
# Optional "quality" parameter (defaults is 3). Fractional values are allowed, for example 1.5. Must be greater than zero.
# Between 0 and 1 = Fast, but mosaic results, closer to 0 increases the mosaic effect.
# 1 = Up to 350 times faster. Poor results, looks very similar to imagecopyresized.
# 2 = Up to 95 times faster. Images appear a little sharp, some prefer this over a quality of 3.
# 3 = Up to 60 times faster. Will give high quality smooth results very close to imagecopyresampled, just faster.
# 4 = Up to 25 times faster. Almost identical to imagecopyresampled for most images.
# 5 = No speedup. Just uses imagecopyresampled, no advantage over imagecopyresampled.
###
if (empty($src_image) || empty($dst_image) || $quality <= 0) { return false; }
if ($quality < 5 && (($dst_w * $quality) < $src_w || ($dst_h * $quality) < $src_h)) {
$temp = imagecreatetruecolor($dst_w * $quality + 1, $dst_h * $quality + 1);
imagecopyresized($temp, $src_image, 0, 0, $src_x, $src_y, $dst_w * $quality + 1, $dst_h * $quality + 1, $src_w, $src_h);
imagecopyresampled($dst_image, $temp, $dst_x, $dst_y, 0, 0, $dst_w, $dst_h, $dst_w * $quality, $dst_h * $quality);
imagedestroy($temp);
} else imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
return true;
}
?>
?>

View File

@ -1,199 +0,0 @@
<?php
/**
* @name Photo Module
* @author Philipp Maurer
* @author Tobias Reich
* @copyright 2014 by Philipp Maurer, Tobias Reich
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
function getPhoto($photoID, $albumID) {
global $database;
$query = "SELECT * FROM lychee_photos WHERE id = '$photoID' LIMIT 1;";
$result = $database->query($query);
$return = $result->fetch_assoc();
if ($albumID!='false') {
if ($return['album']!=0) {
$result = $database->query("SELECT public FROM lychee_albums WHERE id = '" . $return['album'] . "';");
$return_album = $result->fetch_assoc();
if ($return_album['public']=="1") $return['public'] = "2";
}
$return['original_album'] = $return['album'];
$return['album'] = $albumID;
$return['sysdate'] = date('d M. Y', strtotime($return['sysdate']));
if (strlen($return['takedate'])>0) $return['takedate'] = date('d M. Y', strtotime($return['takedate']));
}
unset($return['album_public']);
return $return;
}
function setPhotoPublic($photoID, $url) {
global $database;
$result = $database->query("SELECT public FROM lychee_photos WHERE id = '$photoID';");
$row = $result->fetch_object();
$public = ($row->public==0 ? 1 : 0);
$result = $database->query("UPDATE lychee_photos SET public = '$public' WHERE id = '$photoID';");
if (!$result) return false;
return true;
}
function setPhotoStar($photoIDs) {
global $database;
$error = false;
$result = $database->query("SELECT id, star FROM lychee_photos WHERE id IN ($photoIDs);");
while ($row = $result->fetch_object()) {
$star = ($row->star==0 ? 1 : 0);
$star = $database->query("UPDATE lychee_photos SET star = '$star' WHERE id = '$row->id';");
if (!$star) $error = true;
}
if ($error) return false;
return true;
}
function setPhotoAlbum($photoIDs, $albumID) {
global $database;
$result = $database->query("UPDATE lychee_photos SET album = '$albumID' WHERE id IN ($photoIDs);");
if (!$result) return false;
return true;
}
function setPhotoTitle($photoIDs, $title) {
global $database;
if (strlen($title)>50) return false;
$result = $database->query("UPDATE lychee_photos SET title = '$title' WHERE id IN ($photoIDs);");
if (!$result) return false;
return true;
}
function setPhotoDescription($photoID, $description) {
global $database;
$description = htmlentities($description);
if (strlen($description)>1000) return false;
$result = $database->query("UPDATE lychee_photos SET description = '$description' WHERE id = '$photoID';");
if (!$result) return false;
return true;
}
function setPhotoTags($photoIDs, $tags) {
global $database;
// Parse tags
$tags = preg_replace('/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/', ',', $tags);
$tags = preg_replace('/,$|^,/', ',', $tags);
if (strlen($tags)>1000) return false;
$result = $database->query("UPDATE lychee_photos SET tags = '$tags' WHERE id IN ($photoIDs);");
if (!$result) return false;
return true;
}
function deletePhoto($photoIDs) {
global $database;
$result = $database->query("SELECT id, url, thumbUrl FROM lychee_photos WHERE id IN ($photoIDs);");
while ($row = $result->fetch_object()) {
// Get retina thumb url
$thumbUrl2x = explode(".", $row->thumbUrl);
$thumbUrl2x = $thumbUrl2x[0] . '@2x.' . $thumbUrl2x[1];
// Delete files
if (!unlink('../uploads/big/' . $row->url)) return false;
if (!unlink('../uploads/thumb/' . $row->thumbUrl)) return false;
if (!unlink('../uploads/thumb/' . $thumbUrl2x)) return false;
// Delete db entry
$delete = $database->query("DELETE FROM lychee_photos WHERE id = $row->id;");
if (!$delete) return false;
}
if (!$result) return false;
return true;
}
function isPhotoPublic($photoID, $password) {
global $database;
$query = "SELECT public, album FROM lychee_photos WHERE id = '$photoID';";
$result = $database->query($query);
$row = $result->fetch_object();
if ($row->public==1) return true;
else {
$cAP = checkAlbumPassword($row->album, $password);
$iAP = isAlbumPublic($row->album);
if ($iAP&&$cAP) return true;
return false;
}
}
function getPhotoArchive($photoID) {
global $database;
$result = $database->query("SELECT title, url FROM lychee_photos WHERE id = '$photoID';");
$row = $result->fetch_object();
$extension = array_reverse(explode('.', $row->url));
if ($row->title=='') $row->title = 'Untitled';
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$row->title.$extension[0]\"");
header("Content-Length: " . filesize("../uploads/big/$row->url"));
readfile("../uploads/big/$row->url");
return true;
}
?>

View File

@ -1,69 +0,0 @@
<?php
/**
* @name Session Module
* @author Philipp Maurer
* @author Tobias Reich
* @copyright 2014 by Philipp Maurer, Tobias Reich
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
function init($mode, $version) {
global $settings, $configVersion;
// Update
if ($configVersion!==$version)
if (!update($version)) exit('Error: Updating the database failed!');
$return['config'] = $settings;
unset($return['config']['password']);
// No login
if ($settings['username']===''&&$settings['password']==='') $return['config']['login'] = false;
else $return['config']['login'] = true;
if ($mode==='admin') {
$return['loggedIn'] = true;
} else {
unset($return['config']['username']);
unset($return['config']['thumbQuality']);
unset($return['config']['sorting']);
unset($return['config']['dropboxKey']);
unset($return['config']['login']);
$return['loggedIn'] = false;
}
return $return;
}
function login($username, $password) {
global $database, $settings;
// Check login
if ($username===$settings['username']&&$password===$settings['password']) {
$_SESSION['login'] = true;
return true;
}
// No login
if ($settings['username']===''&&$settings['password']==='') {
$_SESSION['login'] = true;
return true;
}
return false;
}
function logout() {
session_destroy();
return true;
}
?>

View File

@ -1,136 +0,0 @@
<?php
/**
* @name Settings Module
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
function getSettings() {
global $database;
$result = $database->query('SELECT * FROM lychee_settings;');
while($row = $result->fetch_object()) {
$return[$row->key] = $row->value;
}
return $return;
}
function setLogin($oldPassword = '', $username, $password) {
global $settings;
if ($oldPassword==$settings['password']) {
if (!setUsername($username)) exit('Error: Updating username failed!');
if (!setPassword($password)) exit('Error: Updating password failed!');
return true;
}
exit('Error: Current password entered incorrectly!');
}
function setUsername($username) {
global $database;
$username = htmlentities($username);
if (strlen($username)>50) return false;
$result = $database->query("UPDATE lychee_settings SET value = '$username' WHERE `key` = 'username';");
if (!$result) return false;
return true;
}
function setPassword($password) {
global $database;
if (strlen($password)<1||strlen($password)>50) return false;
$result = $database->query("UPDATE lychee_settings SET value = '$password' WHERE `key` = 'password';");
if (!$result) return false;
return true;
}
function setDropboxKey($key) {
global $database;
if (strlen($key)<1||strlen($key)>50) return false;
$result = $database->query("UPDATE lychee_settings SET value = '$key' WHERE `key` = 'dropboxKey';");
if (!$result) return false;
return true;
}
function setSorting($type, $order) {
global $database;
$sorting = 'ORDER BY ';
switch ($type) {
case 'id': $sorting .= 'id';
break;
case 'title': $sorting .= 'title';
break;
case 'description': $sorting .= 'description';
break;
case 'public': $sorting .= 'public';
break;
case 'type': $sorting .= 'type';
break;
case 'star': $sorting .= 'star';
break;
case 'take': $sorting .= 'UNIX_TIMESTAMP(STR_TO_DATE(CONCAT(takedate,"-",taketime),"%d.%m.%Y-%H:%i:%S"))';
break;
default: exit('Error: Unknown type for sorting!');
}
$sorting .= ' ';
switch ($order) {
case 'ASC': $sorting .= 'ASC';
break;
case 'DESC': $sorting .= 'DESC';
break;
default: exit('Error: Unknown order for sorting!');
}
$result = $database->query("UPDATE lychee_settings SET value = '$sorting' WHERE `key` = 'sorting';");
if (!$result) return false;
return true;
}
?>

View File

@ -1,407 +0,0 @@
<?php
/**
* @name Upload Module
* @author Philipp Maurer
* @author Tobias Reich
* @copyright 2014 by Philipp Maurer, Tobias Reich
*/
if (!defined('LYCHEE')) exit('Error: Direct access is not allowed!');
function upload($files, $albumID) {
global $database, $settings;
switch($albumID) {
// s for public (share)
case 's':
$public = 1;
$star = 0;
$albumID = 0;
break;
// f for starred (fav)
case 'f':
$star = 1;
$public = 0;
$albumID = 0;
break;
default:
$star = 0;
$public = 0;
}
foreach ($files as $file) {
if ($file['type']!=='image/jpeg'&&
$file['type']!=='image/png'&&
$file['type']!=='image/gif')
return false;
$id = str_replace('.', '', microtime(true));
while(strlen($id)<14) $id .= 0;
$tmp_name = $file['tmp_name'];
$extension = array_reverse(explode('.', $file['name']));
$extension = $extension[0];
$photo_name = md5($id) . ".$extension";
// Import if not uploaded via web
if (!is_uploaded_file($tmp_name)) {
if (copy($tmp_name, '../uploads/big/' . $photo_name)) {
@unlink($tmp_name);
$import_name = $tmp_name;
}
} else {
move_uploaded_file($tmp_name, '../uploads/big/' . $photo_name);
$import_name = '';
}
// Read infos
$info = getInfo($photo_name);
// Use title of file if IPTC title missing
if ($info['title']==='')
$info['title'] = mysqli_real_escape_string($database, substr(basename($file['name'], ".$extension"), 0, 30));
// Set orientation based on EXIF data
if ($file['type']==='image/jpeg'&&isset($info['orientation'])&&isset($info['width'])&&isset($info['height'])) {
if ($info['orientation']==3||$info['orientation']==6||$info['orientation']==8) {
$newWidth = $info['width'];
$newHeight = $info['height'];
$sourceImg = imagecreatefromjpeg("../uploads/big/$photo_name");
switch($info['orientation']){
case 2:
// mirror
// not yet implemented
break;
case 3:
$sourceImg = imagerotate($sourceImg, -180, 0);
break;
case 4:
// rotate 180 and mirror
// not yet implemented
break;
case 5:
// rotate 90 and mirror
// not yet implemented
break;
case 6:
$sourceImg = imagerotate($sourceImg, -90, 0);
$newWidth = $info['height'];
$newHeight = $info['width'];
break;
case 7:
// rotate -90 and mirror
// not yet implemented
break;
case 8:
$sourceImg = imagerotate($sourceImg, 90, 0);
$newWidth = $info['height'];
$newHeight = $info['width'];
break;
}
$newSourceImg = imagecreatetruecolor($newWidth, $newHeight);
imagecopyresampled($newSourceImg, $sourceImg, 0, 0, 0, 0, $newWidth, $newHeight, $newWidth, $newHeight);
imagejpeg($newSourceImg, "../uploads/big/$photo_name", 100);
}
}
// Create Thumb
if (!createThumb($photo_name)) return false;
// Save to DB
$query = "INSERT INTO lychee_photos (id, title, url, description, type, width, height, size, sysdate, systime, iso, aperture, make, model, shutter, focal, takedate, taketime, thumbUrl, album, public, star, import_name)
VALUES (
'" . $id . "',
'" . $info['title'] . "',
'" . $photo_name . "',
'" . $info['description'] . "',
'" . $info['type'] . "',
'" . $info['width'] . "',
'" . $info['height'] . "',
'" . $info['size'] . "',
'" . $info['date'] . "',
'" . $info['time'] . "',
'" . $info['iso'] . "',
'" . $info['aperture'] . "',
'" . $info['make'] . "',
'" . $info['model'] . "',
'" . $info['shutter'] . "',
'" . $info['focal'] . "',
'" . $info['takeDate'] . "',
'" . $info['takeTime'] . "',
'" . md5($id) . ".jpeg',
'" . $albumID . "',
'" . $public . "',
'" . $star . "',
'" . $import_name . "');";
$result = $database->query($query);
if (!$result) return false;
}
return true;
}
function getInfo($filename) {
global $database;
$url = '../uploads/big/' . $filename;
$iptcArray = array();
$info = getimagesize($url, $iptcArray);
// General information
$return['type'] = $info['mime'];
$return['width'] = $info[0];
$return['height'] = $info[1];
$return['date'] = date('d.m.Y', filectime($url));
$return['time'] = date('H:i:s', filectime($url));
// Size
$size = filesize($url)/1024;
if ($size>=1024) $return['size'] = round($size/1024, 1) . ' MB';
else $return['size'] = round($size, 1) . ' KB';
// IPTC Metadata Fallback
$return['title'] = '';
$return['description'] = '';
// IPTC Metadata
if(isset($iptcArray['APP13'])) {
$iptcInfo = iptcparse($iptcArray['APP13']);
if (is_array($iptcInfo)) {
$temp = @$iptcInfo['2#105'][0];
if (isset($temp)&&strlen($temp)>0) $return['title'] = $temp;
$temp = @$iptcInfo['2#120'][0];
if (isset($temp)&&strlen($temp)>0) $return['description'] = $temp;
}
}
// EXIF Metadata Fallback
$return['orientation'] = '';
$return['iso'] = '';
$return['aperture'] = '';
$return['make'] = '';
$return['model'] = '';
$return['shutter'] = '';
$return['focal'] = '';
$return['takeDate'] = '';
$return['takeTime'] = '';
// Read EXIF
if ($info['mime']=='image/jpeg') $exif = @exif_read_data($url, 'EXIF', 0);
else $exif = false;
// EXIF Metadata
if ($exif!==false) {
$temp = @$exif['Orientation'];
if (isset($temp)) $return['orientation'] = $temp;
$temp = @$exif['ISOSpeedRatings'];
if (isset($temp)) $return['iso'] = $temp;
$temp = @$exif['COMPUTED']['ApertureFNumber'];
if (isset($temp)) $return['aperture'] = $temp;
$temp = @$exif['Make'];
if (isset($temp)) $return['make'] = $exif['Make'];
$temp = @$exif['Model'];
if (isset($temp)) $return['model'] = $temp;
$temp = @$exif['ExposureTime'];
if (isset($temp)) $return['shutter'] = $exif['ExposureTime'] . ' Sec.';
$temp = @$exif['FocalLength'];
if (isset($temp)) $return['focal'] = ($temp/1) . ' mm';
$temp = @$exif['DateTimeOriginal'];
if (isset($temp)) {
$exifDate = explode(' ', $temp);
$date = explode(':', $exifDate[0]);
$return['takeDate'] = $date[2].'.'.$date[1].'.'.$date[0];
$return['takeTime'] = $exifDate[1];
}
}
// Security
foreach(array_keys($return) as $key) $return[$key] = mysqli_real_escape_string($database, $return[$key]);
return $return;
}
function createThumb($filename, $width = 200, $height = 200) {
global $settings;
$url = "../uploads/big/$filename";
$info = getimagesize($url);
$photoName = explode(".", $filename);
$newUrl = "../uploads/thumb/$photoName[0].jpeg";
$newUrl2x = "../uploads/thumb/$photoName[0]@2x.jpeg";
// Set position and size
$thumb = imagecreatetruecolor($width, $height);
$thumb2x = imagecreatetruecolor($width*2, $height*2);
if ($info[0]<$info[1]) {
$newSize = $info[0];
$startWidth = 0;
$startHeight = $info[1]/2 - $info[0]/2;
} else {
$newSize = $info[1];
$startWidth = $info[0]/2 - $info[1]/2;
$startHeight = 0;
}
// Fallback for older version
if ($info['mime']==='image/webp'&&floatval(phpversion())<5.5) return false;
// Create new image
switch($info['mime']) {
case 'image/jpeg': $sourceImg = imagecreatefromjpeg($url); break;
case 'image/png': $sourceImg = imagecreatefrompng($url); break;
case 'image/gif': $sourceImg = imagecreatefromgif($url); break;
case 'image/webp': $sourceImg = imagecreatefromwebp($url); break;
default: return false;
}
imagecopyresampled($thumb,$sourceImg,0,0,$startWidth,$startHeight,$width,$height,$newSize,$newSize);
imagecopyresampled($thumb2x,$sourceImg,0,0,$startWidth,$startHeight,$width*2,$height*2,$newSize,$newSize);
imagejpeg($thumb,$newUrl,$settings['thumbQuality']);
imagejpeg($thumb2x,$newUrl2x,$settings['thumbQuality']);
return true;
}
function importPhoto($path, $albumID = 0) {
$info = getimagesize($path);
$size = filesize($path);
$nameFile = array(array());
$nameFile[0]['name'] = $path;
$nameFile[0]['type'] = $info['mime'];
$nameFile[0]['tmp_name'] = $path;
$nameFile[0]['error'] = 0;
$nameFile[0]['size'] = $size;
return upload($nameFile, $albumID);
}
function importUrl($url, $albumID = 0) {
if (strpos($url, ',')!==false) {
// Multiple photos
$url = explode(',', $url);
foreach ($url as &$key) {
$key = str_replace(' ', '%20', $key);
if (@getimagesize($key)) {
$pathinfo = pathinfo($key);
$filename = $pathinfo['filename'].".".$pathinfo['extension'];
$tmp_name = "../uploads/import/$filename";
copy($key, $tmp_name);
}
}
return importServer($albumID);
} else {
// One photo
$url = str_replace(' ', '%20', $url);
if (@getimagesize($url)) {
$pathinfo = pathinfo($url);
$filename = $pathinfo['filename'].".".$pathinfo['extension'];
$tmp_name = "../uploads/import/$filename";
copy($url, $tmp_name);
return importPhoto($tmp_name, $albumID);
}
}
return false;
}
function importServer($albumID = 0, $path = '../uploads/import/') {
global $database;
$files = glob($path . '*');
$contains['photos'] = false;
$contains['albums'] = false;
foreach ($files as $file) {
if (@getimagesize($file)) {
// Photo
if (!importPhoto($file, $albumID)) return false;
$contains['photos'] = true;
} else if (is_dir($file)) {
$name = mysqli_real_escape_string($database, basename($file));
$newAlbumID = addAlbum('[Import] ' . $name);
if ($newAlbumID!==false) importServer($newAlbumID, $file . '/');
$contains['albums'] = true;
}
}
if ($contains['photos']===false&&$contains['albums']===false) return "Warning: Folder empty!";
if ($contains['photos']===false&&$contains['albums']===true) return "Notice: Import only contains albums!";
return true;
}
?>

View File

@ -1,70 +0,0 @@
<?php
/**
* @name check.php
* @author Tobias Reich
* @copyright 2014 by Tobias Reich
* @description This file takes a look at your Lychee-configuration and displays all errors it can find.
*/
define('LYCHEE', true);
header('content-type: text/plain');
// Declare
$error = '';
// Include
if (!file_exists('../data/config.php')) exit('Error 001: Configuration not found. Please install Lychee first.');
require('../data/config.php');
require('../php/modules/settings.php');
// Database
$database = new mysqli($dbHost, $dbUser, $dbPassword, $dbName);
if (mysqli_connect_errno()!=0) $error .= ('Error 100: ' . mysqli_connect_errno() . ': ' . mysqli_connect_error() . '' . PHP_EOL);
// Get Settings
$settings = getSettings();
// PHP Version
if (floatval(phpversion())<5.2) $error .= ('Error 200: Please upgrade to PHP 5.2 or higher!' . PHP_EOL);
// Extensions
if (!extension_loaded('exif')) $error .= ('Error 300: PHP exif extension not activated' . PHP_EOL);
if (!extension_loaded('mbstring')) $error .= ('Error 301: PHP mbstring extension not activated' . PHP_EOL);
if (!extension_loaded('gd')) $error .= ('Error 302: PHP gd extension not activated' . PHP_EOL);
if (!extension_loaded('mysqli')) $error .= ('Error 303: PHP mysqli extension not activated' . PHP_EOL);
if (!extension_loaded('json')) $error .= ('Error 304: PHP json extension not activated' . PHP_EOL);
if (!extension_loaded('zip')) $error .= ('Error 305: PHP zip extension not activated' . PHP_EOL);
// Config
if (!isset($dbName)||$dbName=='') $error .= ('Error 400: No property for $dbName in config.php' . PHP_EOL);
if (!isset($dbUser)||$dbUser=='') $error .= ('Error 401: No property for $dbUser in config.php' . PHP_EOL);
if (!isset($dbPassword)) $error .= ('Error 402: No property for $dbPassword in config.php' . PHP_EOL);
if (!isset($dbHost)||$dbHost=='') $error .= ('Error 403: No property for $dbHost in config.php' . PHP_EOL);
// Database Config
if (!$settings['username']||$settings['username']=='') $error .= ('Error 404: Username empty or not set' . PHP_EOL);
if (!$settings['password']||$settings['password']=='') $error .= ('Error 405: Password empty or not set' . PHP_EOL);
if (!$settings['thumbQuality']||$settings['thumbQuality']=='') $error .= ('Error 406: No or wrong property for thumbQuality' . PHP_EOL);
if (!$settings['sorting']||$settings['sorting']=='') $error .= ('Error 407: Wrong property for sorting' . PHP_EOL);
if (!$settings['checkForUpdates']||($settings['checkForUpdates']!='0'&&$settings['checkForUpdates']!='1')) $error .= ('Error 408: No or wrong property for checkForUpdates' . PHP_EOL);
// Permissions
if (substr(sprintf('%o', @fileperms('../uploads/big/')), -4)!='0777') $error .= ('Error 500: Wrong permissions for \'uploads/big\' (777 required)' . PHP_EOL);
if (substr(sprintf('%o', @fileperms('../uploads/thumb/')), -4)!='0777') $error .= ('Error 501: Wrong permissions for \'uploads/thumb\' (777 required)' . PHP_EOL);
if (substr(sprintf('%o', @fileperms('../uploads/import/')), -4)!='0777')$error .= ('Error 502: Wrong permissions for \'uploads/import\' (777 required)' . PHP_EOL);
if (substr(sprintf('%o', @fileperms('../uploads/')), -4)!='0777') $error .= ('Error 503: Wrong permissions for \'uploads/\' (777 required)' . PHP_EOL);
if (substr(sprintf('%o', @fileperms('../data/')), -4)!='0777') $error .= ('Error 504: Wrong permissions for \'data/\' (777 required)' . PHP_EOL);
if ($error=='') echo('Everything is fine. Lychee should work without problems!' . PHP_EOL . PHP_EOL); else echo $error;
// Check dropboxKey
if (!$settings['dropboxKey']) echo('Warning: Dropbox import not working. No property for dropboxKey.' . PHP_EOL);
// Check php.ini Settings
if (ini_get('max_execution_time')<200&&ini_set('upload_max_filesize', '20M')===false) echo('Warning: You may experience problems when uploading a large amount of photos. Take a look in the FAQ for details.' . PHP_EOL);
// Check mysql version
if ($database->server_version<50500) echo('Warning: Lychee uses the GBK charset to avoid sql injections on your MySQL version. Please update to MySQL 5.5 or higher to enable UTF-8 support.' . PHP_EOL);
?>

81
plugins/check/index.php Normal file
View File

@ -0,0 +1,81 @@
<?php
###
# @name Check Plugin
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
# @description This file takes a look at your Lychee-configuration and displays all errors it can find.
###
# Location
$lychee = __DIR__ . '/../../';
# Load requirements
require($lychee . 'php/define.php');
require($lychee . 'php/autoload.php');
require($lychee . 'php/modules/misc.php');
# Set content
header('content-type: text/plain');
# Declare
$error = '';
# Load config
if (!file_exists(LYCHEE_CONFIG_FILE)) exit('Error 001: Configuration not found. Please install Lychee first.');
require(LYCHEE_CONFIG_FILE);
# Database
$database = new mysqli($dbHost, $dbUser, $dbPassword, $dbName);
if (mysqli_connect_errno()!=0) $error .= ('Error 100: ' . mysqli_connect_errno() . ': ' . mysqli_connect_error() . '' . PHP_EOL);
# Load settings
$settings = new Settings($database);
$settings = $settings->get();
# PHP Version
if (floatval(phpversion())<5.2) $error .= ('Error 200: Please upgrade to PHP 5.2 or higher!' . PHP_EOL);
# Extensions
if (!extension_loaded('exif')) $error .= ('Error 300: PHP exif extension not activated' . PHP_EOL);
if (!extension_loaded('mbstring')) $error .= ('Error 301: PHP mbstring extension not activated' . PHP_EOL);
if (!extension_loaded('gd')) $error .= ('Error 302: PHP gd extension not activated' . PHP_EOL);
if (!extension_loaded('mysqli')) $error .= ('Error 303: PHP mysqli extension not activated' . PHP_EOL);
if (!extension_loaded('json')) $error .= ('Error 304: PHP json extension not activated' . PHP_EOL);
if (!extension_loaded('zip')) $error .= ('Error 305: PHP zip extension not activated' . PHP_EOL);
# Config
if (!isset($dbName)||$dbName==='') $error .= ('Error 400: No property for $dbName in config.php' . PHP_EOL);
if (!isset($dbUser)||$dbUser==='') $error .= ('Error 401: No property for $dbUser in config.php' . PHP_EOL);
if (!isset($dbPassword)) $error .= ('Error 402: No property for $dbPassword in config.php' . PHP_EOL);
if (!isset($dbHost)||$dbHost==='') $error .= ('Error 403: No property for $dbHost in config.php' . PHP_EOL);
# Settings
if (!isset($settings['username'])||$settings['username']=='') $error .= ('Error 404: Username empty or not set in database' . PHP_EOL);
if (!isset($settings['password'])||$settings['password']=='') $error .= ('Error 405: Password empty or not set in database' . PHP_EOL);
if (!isset($settings['thumbQuality'])||$settings['thumbQuality']=='') $error .= ('Error 406: No or wrong property for thumbQuality in database' . PHP_EOL);
if (!isset($settings['sorting'])||$settings['sorting']=='') $error .= ('Error 407: Wrong property for sorting in database' . PHP_EOL);
if (!isset($settings['plugins'])) $error .= ('Error 408: No property for plugins in database' . PHP_EOL);
if (!isset($settings['checkForUpdates'])||($settings['checkForUpdates']!='0'&&$settings['checkForUpdates']!='1')) $error .= ('Error 409: No or wrong property for checkForUpdates in database' . PHP_EOL);
# Permissions
if (hasPermissions(LYCHEE_UPLOADS_BIG)===false) $error .= ('Error 500: Wrong permissions for \'uploads/big\' (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_THUMB)===false) $error .= ('Error 501: Wrong permissions for \'uploads/thumb\' (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS_IMPORT)===false) $error .= ('Error 502: Wrong permissions for \'uploads/import\' (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_UPLOADS)===false) $error .= ('Error 503: Wrong permissions for \'uploads/\' (777 required)' . PHP_EOL);
if (hasPermissions(LYCHEE_DATA)===false) $error .= ('Error 504: Wrong permissions for \'data/\' (777 required)' . PHP_EOL);
# Output
if ($error=='') echo('Everything is fine. Lychee should work without problems!' . PHP_EOL . PHP_EOL);
else echo $error;
# Check dropboxKey
if (!$settings['dropboxKey']) echo('Warning: Dropbox import not working. No property for dropboxKey.' . PHP_EOL);
# Check php.ini Settings
if (ini_get('max_execution_time')<200&&ini_set('upload_max_filesize', '20M')===false) echo('Warning: You may experience problems when uploading a large amount of photos. Take a look in the FAQ for details.' . PHP_EOL);
# Check mysql version
if ($database->server_version<50500) echo('Warning: Lychee uses the GBK charset to avoid sql injections on your MySQL version. Please update to MySQL 5.5 or higher to enable UTF-8 support.' . PHP_EOL);
?>

View File

@ -0,0 +1,54 @@
<?php
###
# @name Display Log Plugin
# @author Tobias Reich
# @copyright 2014 by Tobias Reich
# @description This file queries the database for log messages and displays them if present.
###
# Location
$lychee = __DIR__ . '/../../';
# Load requirements
require($lychee . 'php/define.php');
require($lychee . 'php/autoload.php');
require($lychee . 'php/modules/misc.php');
# Set content
header('content-type: text/plain');
# Load config
if (!file_exists(LYCHEE_CONFIG_FILE)) exit('Error 001: Configuration not found. Please install Lychee first.');
require(LYCHEE_CONFIG_FILE);
# Declare
$result = '';
# Database
$database = new mysqli($dbHost, $dbUser, $dbPassword, $dbName);
if (mysqli_connect_errno()!=0) {
echo 'Error 100: ' . mysqli_connect_errno() . ': ' . mysqli_connect_error() . '' . PHP_EOL;
exit();
}
# Result
$result = $database->query('SELECT FROM_UNIXTIME(time), type, function, line, text FROM lychee_log;');
# Output
if ($result === FALSE) {
echo('Everything looks fine, Lychee has not reported any problems!' . PHP_EOL . PHP_EOL);
} else {
while ( $row = $result->fetch_row() ) {
# Encode result before printing
$row = array_map("htmlentities", $row);
# Format: time TZ - type - function(line) - text
printf ("%s %s - %s - %s (%s) \t- %s\n", $row[0], date_default_timezone_get(), $row[1], $row[2], $row[3], $row[4]);
}
}
?>

View File

@ -39,11 +39,13 @@ In order to use the Dropbox import from your server, you need a valid drop-ins a
Lychee supports [Twitter Cards](https://dev.twitter.com/docs/cards) and [Open Graph](http://opengraphprotocol.org) for shared images (not albums). In order to use Twitter Cards you need to request an approval for your domain. Simply share an image with Lychee, copy its link and paste it in [Twitters Card Validator](https://dev.twitter.com/docs/cards/validation/validator).
## Troubleshooting
### Plugins and Extensions
Take a look at the [FAQ](docs/FAQ.md) if you have problems. Discovered a bug? Please create an issue here on GitHub!
The plugin-system of Lychee allows you to execute scripts, when a certain action fires. Plugins are hooks, which are injected directly into Lychee. [Plugin documentation &#187;](docs/)
## Extensions
It's also possible to build extensions upon Lychee. The way to do so isn't documented and can change every time. We recommend to use the plugin-system, when possible.
Here's a list of all available Plugins and Extensions:
| Name | Description | |
|:-----------|:------------|:------------|
@ -51,6 +53,10 @@ Take a look at the [FAQ](docs/FAQ.md) if you have problems. Discovered a bug? Pl
| Jekyll | Liquid tag for Jekyll sites that allows embedding Lychee albums | [More &#187;](https://gist.github.com/tobru/9171700) |
| lychee-redirect | Redirect from an album-name to a Lychee-album | [More &#187;](https://github.com/electerious/lychee-redirect) |
## Troubleshooting
Take a look at the [FAQ](docs/FAQ.md) if you have problems. Discovered a bug? Please create an issue here on GitHub!
## Developer
| Version | Name |
|:-----------|:------------|

View File

View File

@ -3,33 +3,35 @@
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title></title>
<title>Lychee</title>
<meta name="author" content="Tobias Reich, Philipp Maurer">
<meta name="author" content="Tobias Reich">
<meta name="keywords" content="">
<meta name="description" content="">
<link type="text/css" rel="stylesheet" href="assets/build/main.css">
<!-- CSS -->
<link type="text/css" rel="stylesheet" href="assets/min/main.css">
<link rel="shortcut icon" href="favicon.ico">
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-iphone.png" sizes="120x120">
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-ipad.png" sizes="152x152">
<meta name="apple-mobile-web-app-status-bar-style" content="black" >
<meta name="viewport" content="user-scalable=no, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1.0, maximum-scale=1.0">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-capable" content="yes">
<?php
if(isset($_GET['p'])) {
if (isset($_GET['p'])&&$_GET['p']>0) {
define("LYCHEE", true);
require(__DIR__ . "/php/define.php");
require(LYCHEE_CONFIG_FILE);
require(LYCHEE . "php/autoload.php");
require(LYCHEE . "php/modules/misc.php");
require("data/config.php");
require("php/modules/db.php");
require("php/modules/misc.php");
$database = Database::connect($dbHost, $dbUser, $dbPassword, $dbName);
$database = dbConnect();
echo openGraphHeader($_GET['p']);
echo getGraphHeader($database, $_GET['p']);
}
@ -56,9 +58,7 @@
<div id="infobox"></div>
<!-- JS -->
<script type="text/javascript" src="assets/js/_frameworks.js"></script>
<script type="text/javascript" src="assets/js/build.js"></script>
<script type="text/javascript" src="assets/js/view/main.js"></script>
<script type="text/javascript" src="assets/min/view.js"></script>
</body>
</html>