From e942c9c5254b368a9a0f0298f34d97d02b44b4e5 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 22 Aug 2014 22:02:58 +0200 Subject: [PATCH 01/63] Don't reupload duplicates (#48) --- php/modules/Photo.php | 80 ++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/php/modules/Photo.php b/php/modules/Photo.php index 506f5d6..adc2fc4 100755 --- a/php/modules/Photo.php +++ b/php/modules/Photo.php @@ -96,26 +96,53 @@ class Photo extends Module { $id = str_replace('.', '', microtime(true)); while(strlen($id)<14) $id .= 0; + # Set paths $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); + # Calculate checksum + $checksum = sha1_file($tmp_name); + + # Check if image exists based on checksum + if ($checksum===false) { + + $checksum = ''; + $exists = false; + } 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!'); + + $query = "SELECT id, url, thumbUrl FROM lychee_photos WHERE checksum = '$checksum' LIMIT 1;"; + $result = $this->database->query($query); + + if ($result->num_rows===1) { + $result = $result->fetch_assoc(); + $photo_name = $result['url']; + $path = LYCHEE_UPLOADS_BIG . $result['url']; + $path_thumb = $result['thumbUrl']; + $exists = true; + } else { + $exists = false; } + } - # Calculate checksum - $checksum = sha1_file($path); - if ($checksum===false) $checksum = ''; + if ($exists===false) { + + # 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); @@ -126,18 +153,25 @@ class Photo extends Module { # 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'] . ')'); - } + if ($exists===false) { - # Set original date - if ($info['takestamp']!=='') @touch($path, $info['takestamp']); + # 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!'); + } + + # Set thumb url + $path_thumb = md5($id) . '.jpeg'; - # 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 @@ -159,7 +193,7 @@ class Photo extends Module { '" . $info['shutter'] . "', '" . $info['focal'] . "', '" . $info['takestamp'] . "', - '" . md5($id) . ".jpeg', + '" . $path_thumb . "', '" . $albumID . "', '" . $public . "', '" . $star . "', From 11e8b8b2e6e5e384129aeacfa494d65b3f1913fc Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 22 Aug 2014 22:14:50 +0200 Subject: [PATCH 02/63] Moved check for duplicates into its own function --- php/modules/Photo.php | 45 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/php/modules/Photo.php b/php/modules/Photo.php index adc2fc4..4089ac0 100755 --- a/php/modules/Photo.php +++ b/php/modules/Photo.php @@ -112,17 +112,13 @@ class Photo extends Module { } else { - $query = "SELECT id, url, thumbUrl FROM lychee_photos WHERE checksum = '$checksum' LIMIT 1;"; - $result = $this->database->query($query); + $exists = $this->exists($checksum); - if ($result->num_rows===1) { - $result = $result->fetch_assoc(); - $photo_name = $result['url']; - $path = LYCHEE_UPLOADS_BIG . $result['url']; - $path_thumb = $result['thumbUrl']; + if ($exists!==false) { + $photo_name = $exists['photo_name']; + $path = $exists['path']; + $path_thumb = $exists['path_thumb']; $exists = true; - } else { - $exists = false; } } @@ -214,6 +210,37 @@ class Photo extends Module { } + private function exists($checksum) { + + # Check dependencies + self::dependencies(isset($this->database, $checksum)); + + $query = "SELECT id, url, thumbUrl FROM lychee_photos WHERE checksum = '$checksum' LIMIT 1;"; + $result = $this->database->query($query); + + if (!$result) { + Log::error($this->database, __METHOD__, __LINE__, 'Could not check for existing photos with the same checksum'); + return false; + } + + if ($result->num_rows===1) { + + $result = $result->fetch_assoc(); + + $return = array( + 'photo_name' => $result['url'], + 'path' => LYCHEE_UPLOADS_BIG . $result['url'], + 'path_thumb' => $result['thumbUrl'], + ); + + return $return; + + } + + return false; + + } + private function createThumb($url, $filename, $width = 200, $height = 200) { # Check dependencies From 47e32199660e7aeeb51feb8fa43ec85a4e6b5058 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 22 Aug 2014 22:34:59 +0200 Subject: [PATCH 03/63] Use fetch_object --- php/modules/Photo.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/php/modules/Photo.php b/php/modules/Photo.php index 4089ac0..593e9e0 100755 --- a/php/modules/Photo.php +++ b/php/modules/Photo.php @@ -225,12 +225,12 @@ class Photo extends Module { if ($result->num_rows===1) { - $result = $result->fetch_assoc(); + $result = $result->fetch_object(); $return = array( - 'photo_name' => $result['url'], - 'path' => LYCHEE_UPLOADS_BIG . $result['url'], - 'path_thumb' => $result['thumbUrl'], + 'photo_name' => $result->url, + 'path' => LYCHEE_UPLOADS_BIG . $result->url, + 'path_thumb' => $result->thumbUrl ); return $return; From 988a9075f3c628ed483fa4c4eb5fb074cd26668d Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 22 Aug 2014 22:54:33 +0200 Subject: [PATCH 04/63] Don't delete photo when used elsewhere --- php/modules/Photo.php | 49 +++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/php/modules/Photo.php b/php/modules/Photo.php index 593e9e0..a1055ef 100755 --- a/php/modules/Photo.php +++ b/php/modules/Photo.php @@ -210,12 +210,15 @@ class Photo extends Module { } - private function exists($checksum) { + private function exists($checksum, $photoID = null) { # Check dependencies self::dependencies(isset($this->database, $checksum)); - $query = "SELECT id, url, thumbUrl FROM lychee_photos WHERE checksum = '$checksum' LIMIT 1;"; + # Exclude $photoID from select when $photoID is set + if (isset($photoID)) $query = "SELECT id, url, thumbUrl FROM lychee_photos WHERE checksum = '$checksum' AND id <> '$photoID' LIMIT 1;"; + else $query = "SELECT id, url, thumbUrl FROM lychee_photos WHERE checksum = '$checksum' LIMIT 1;"; + $result = $this->database->query($query); if (!$result) { @@ -820,7 +823,7 @@ class Photo extends Module { $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);"); + $photos = $this->database->query("SELECT id, url, thumbUrl, checksum FROM lychee_photos WHERE id IN ($this->photoIDs);"); if (!$photos) { Log::error($this->database, __METHOD__, __LINE__, $this->database->error); return false; @@ -829,26 +832,32 @@ class Photo extends Module { # For each photo while ($photo = $photos->fetch_object()) { - # Get retina thumb url - $thumbUrl2x = explode(".", $photo->thumbUrl); - $thumbUrl2x = $thumbUrl2x[0] . '@2x.' . $thumbUrl2x[1]; + # Check if other photos are referring to this images + # If so, only delete the db entry + if ($this->exists($photo->checksum, $photo->id)===false) { - # 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; - } + # Get retina thumb url + $thumbUrl2x = explode(".", $photo->thumbUrl); + $thumbUrl2x = $thumbUrl2x[0] . '@2x.' . $thumbUrl2x[1]; - # 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 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 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 From 45cbf0c238e79e0a0754e9ad5b60cd233370b887 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 22 Aug 2014 23:04:59 +0200 Subject: [PATCH 05/63] Escape --- php/modules/Photo.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/php/modules/Photo.php b/php/modules/Photo.php index a1055ef..06f6b79 100755 --- a/php/modules/Photo.php +++ b/php/modules/Photo.php @@ -215,6 +215,10 @@ class Photo extends Module { # Check dependencies self::dependencies(isset($this->database, $checksum)); + # Escape + $checksum = mysqli_real_escape_string($this->database, $checksum); + if (isset($photoID)) $photoID = mysqli_real_escape_string($this->database, $photoID); + # Exclude $photoID from select when $photoID is set if (isset($photoID)) $query = "SELECT id, url, thumbUrl FROM lychee_photos WHERE checksum = '$checksum' AND id <> '$photoID' LIMIT 1;"; else $query = "SELECT id, url, thumbUrl FROM lychee_photos WHERE checksum = '$checksum' LIMIT 1;"; From c291ab35bf76f5be1b9e522d44b49f67420c23a0 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 22 Aug 2014 23:06:03 +0200 Subject: [PATCH 06/63] Updated version to v2.6.2 --- assets/js/lychee.js | 4 ++-- build/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/js/lychee.js b/assets/js/lychee.js index 4e66af5..2389a80 100644 --- a/assets/js/lychee.js +++ b/assets/js/lychee.js @@ -8,8 +8,8 @@ var lychee = { title: "", - version: "2.6.1", - version_code: "020601", + version: "2.6.2", + version_code: "020602", api_path: "php/api.php", update_path: "http://lychee.electerious.com/version/index.php", diff --git a/build/package.json b/build/package.json index 78553b1..ef7d0c5 100644 --- a/build/package.json +++ b/build/package.json @@ -1,6 +1,6 @@ { "name": "Lychee", - "version": "2.6.1", + "version": "2.6.2", "description": "Self-hosted photo-management done right.", "authors": "Tobias Reich ", "license": "MIT", From d686a2b083105354d64d481fc22f3a4fb794bbf2 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 22 Aug 2014 23:31:09 +0200 Subject: [PATCH 07/63] Duplicate photos by right-clicking them (#186) --- assets/js/contextMenu.js | 12 ++++++++---- assets/js/photo.js | 17 +++++++++++++++++ assets/min/main.js | 12 ++++++------ php/access/Admin.php | 9 +++++++++ php/modules/Photo.php | 35 +++++++++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/assets/js/contextMenu.js b/assets/js/contextMenu.js index 8e0c528..4931780 100644 --- a/assets/js/contextMenu.js +++ b/assets/js/contextMenu.js @@ -164,6 +164,7 @@ contextMenu = { function() { photo.setStar([photoID]) }, function() { photo.editTags([photoID]) }, function() { photo.setTitle([photoID]) }, + function() { photo.duplicate([photoID]) }, function() { contextMenu.move([photoID], e, "right") }, function() { photo.delete([photoID]) } ]; @@ -173,8 +174,9 @@ contextMenu = { [" Tags", 1], ["separator", -1], [" Rename", 2], - [" Move", 3], - [" Delete", 4] + [" Duplicate", 3], + [" Move", 4], + [" Delete", 5] ]; contextMenu.show(items, mouse_x, mouse_y, "right"); @@ -195,6 +197,7 @@ contextMenu = { function() { photo.setStar(photoIDs) }, function() { photo.editTags(photoIDs) }, function() { photo.setTitle(photoIDs) }, + function() { photo.duplicate(photoIDs) }, function() { contextMenu.move(photoIDs, e, "right") }, function() { photo.delete(photoIDs) } ]; @@ -204,8 +207,9 @@ contextMenu = { [" Tag All", 1], ["separator", -1], [" Rename All", 2], - [" Move All", 3], - [" Delete All", 4] + [" Duplicate All", 3], + [" Move All", 4], + [" Delete All", 5] ]; contextMenu.show(items, mouse_x, mouse_y, "right"); diff --git a/assets/js/photo.js b/assets/js/photo.js index 5920363..5686adf 100644 --- a/assets/js/photo.js +++ b/assets/js/photo.js @@ -117,6 +117,23 @@ photo = { }, + duplicate: function(photoIDs) { + + var params; + + if (!photoIDs) return false; + if (photoIDs instanceof Array===false) photoIDs = [photoIDs]; + + params = "duplicatePhoto&photoIDs=" + photoIDs; + lychee.api(params, function(data) { + + if (data!==true) lychee.error(null, params, data); + else album.load(album.getID(), false); + + }); + + }, + delete: function(photoIDs) { var params, diff --git a/assets/min/main.js b/assets/min/main.js index a167628..631478c 100644 --- a/assets/min/main.js +++ b/assets/min/main.js @@ -1,6 +1,6 @@ -function mobileBrowser(){return/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)?!0:!1}function gup(e){e=e.replace(/[\[]/,"\\[").replace(/[\]]/,"\\]");var t="[\\?&]"+e+"=([^&#]*)",n=new RegExp(t),o=n.exec(window.location.href);return null===o?"":o[1]}!function(e,t){"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){function n(e){var t=e.length,n=J.type(e);return"function"===n||J.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e}function o(e,t,n){if(J.isFunction(t))return J.grep(e,function(e,o){return!!t.call(e,o,e)!==n});if(t.nodeType)return J.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return J.filter(t,e,n);t=J.filter(t,e)}return J.grep(e,function(e){return z.call(t,e)>=0!==n})}function i(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function a(e){var t=ft[e]={};return J.each(e.match(ht)||[],function(e,n){t[n]=!0}),t}function r(){Q.removeEventListener("DOMContentLoaded",r,!1),e.removeEventListener("load",r,!1),J.ready()}function s(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=J.expando+Math.random()}function l(e,t,n){var o;if(void 0===n&&1===e.nodeType)if(o="data-"+t.replace(wt,"-$1").toLowerCase(),n=e.getAttribute(o),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:yt.test(n)?J.parseJSON(n):n}catch(i){}bt.set(e,t,n)}else n=void 0;return n}function u(){return!0}function c(){return!1}function d(){try{return Q.activeElement}catch(e){}}function p(e,t){return J.nodeName(e,"table")&&J.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function h(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function f(e){var t=Lt.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n=0,o=e.length;o>n;n++)vt.set(e[n],"globalEval",!t||vt.get(t[n],"globalEval"))}function g(e,t){var n,o,i,a,r,s,l,u;if(1===t.nodeType){if(vt.hasData(e)&&(a=vt.access(e),r=vt.set(t,a),u=a.events)){delete r.handle,r.events={};for(i in u)for(n=0,o=u[i].length;o>n;n++)J.event.add(t,i,u[i][n])}bt.hasData(e)&&(s=bt.access(e),l=J.extend({},s),bt.set(t,l))}}function v(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return void 0===t||t&&J.nodeName(e,t)?J.merge([e],n):n}function b(e,t){var n=t.nodeName.toLowerCase();"input"===n&&kt.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}function y(t,n){var o,i=J(n.createElement(t)).appendTo(n.body),a=e.getDefaultComputedStyle&&(o=e.getDefaultComputedStyle(i[0]))?o.display:J.css(i[0],"display");return i.detach(),a}function w(e){var t=Q,n=Rt[e];return n||(n=y(e,t),"none"!==n&&n||(qt=(qt||J("