diff --git a/.gitignore b/.gitignore
index 96a46c2..95f0487 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
\ No newline at end of file
+!plugins/check/
\ No newline at end of file
diff --git a/assets/build/main.css b/assets/build/main.css
deleted file mode 100644
index ed4e8d8..0000000
--- a/assets/build/main.css
+++ /dev/null
@@ -1 +0,0 @@
-html{font-size:100%}html,body{margin:0;padding:0;border:0;font:inherit;vertical-align:baseline}div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}.fadeIn{-webkit-animation-name:fadeIn;-moz-animation-name:fadeIn;animation-name:fadeIn}.fadeIn,.fadeOut{-webkit-animation-duration:.3s;-webkit-animation-fill-mode:forwards;-webkit-animation-timing-function:cubic-bezier(.51,.92,.24,1.15);-moz-animation-duration:.3s;-moz-animation-fill-mode:forwards;-moz-animation-timing-function:cubic-bezier(.51,.92,.24,1.15);animation-duration:.3s;animation-fill-mode:forwards;animation-timing-function:cubic-bezier(.51,.92,.24,1.15)}.fadeOut{-webkit-animation-name:fadeOut;-moz-animation-name:fadeOut;animation-name:fadeOut}.contentZoomIn{-webkit-animation-name:zoomIn;-moz-animation-name:zoomIn;animation-name:zoomIn}.contentZoomIn,.contentZoomOut{-webkit-animation-duration:.2s;-webkit-animation-fill-mode:forwards;-webkit-animation-timing-function:cubic-bezier(.51,.92,.24,1);-moz-animation-duration:.2s;-moz-animation-fill-mode:forwards;-moz-animation-timing-function:cubic-bezier(.51,.92,.24,1);animation-duration:.2s;animation-fill-mode:forwards;animation-timing-function:cubic-bezier(.51,.92,.24,1)}.contentZoomOut{-webkit-animation-name:zoomOut;-moz-animation-name:zoomOut;animation-name:zoomOut}@-webkit-keyframes moveUp{0%{-webkit-transform:translateY(100px);opacity:0}100%{-webkit-transform:translateY(0);opacity:1}}@-moz-keyframes moveUp{0%{-moz-transform:translateY(100px);opacity:0}100%{-moz-transform:translateY(0);opacity:1}}@keyframes moveUp{0%{transform:translateY(100px);opacity:0}100%{transform:translateY(0);opacity:1}}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@-moz-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@-moz-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@-webkit-keyframes moveBackground{0%{background-position-x:0}100%{background-position-x:-100px}}@-moz-keyframes moveBackground{0%{background-position-x:0}100%{background-position-x:-100px}}@keyframes moveBackground{0%{background-position-x:0}100%{background-position-x:-100px}}@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale(.8)}100%{opacity:1;-webkit-transform:scale(1)}}@-moz-keyframes zoomIn{0%{opacity:0;-moz-transform:scale(.8)}100%{opacity:1;-moz-transform:scale(1)}}@keyframes zoomIn{0%{opacity:0;transform:scale(.8)}100%{opacity:1;transform:scale(1)}}@-webkit-keyframes zoomOut{0%{opacity:1;-webkit-transform:scale(1)}100%{opacity:0;-webkit-transform:scale(.8)}}@-moz-keyframes zoomOut{0%{opacity:1;-moz-transform:scale(1)}100%{opacity:0;-moz-transform:scale(.8)}}@keyframes zoomOut{0%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(.8)}}@-webkit-keyframes popIn{0%{opacity:0;-webkit-transform:scale(0)}100%{opacity:1;-webkit-transform:scale(1)}}@-moz-keyframes popIn{0%{opacity:0;-moz-transform:scale(0)}100%{opacity:1;-moz-transform:scale(1)}}@keyframes popIn{0%{opacity:0;transform:scale(0)}100%{opacity:1;transform:scale(1)}}@-webkit-keyframes pulse{0%{opacity:1}50%{opacity:.3}100%{opacity:1}}@-moz-keyframes pulse{0%{opacity:1}50%{opacity:.3}100%{opacity:1}}@keyframes pulse{0%{opacity:1}50%{opacity:.3}100%{opacity:1}}#content::before{content:"";position:absolute;left:0;width:100%;height:20px;background-image:-webkit-linear-gradient(top,#262626,#222);background-image:-moz-linear-gradient(top,#262626,#222);background-image:-ms-linear-gradient(top,#262626,#222);background-image:linear-gradient(top,#262626,#222);border-top:1px solid #333}#content.view::before{display:none}#content{position:absolute;padding:50px 0 33px;width:100%;min-height:calc(100% - 90px);-webkit-overflow-scrolling:touch}.photo{float:left;display:inline-block;width:206px;height:206px;margin:30px 0 0 30px;cursor:pointer}.photo img{position:absolute;width:200px;height:200px;background-color:#222;border-radius:2px;border:2px solid #ccc}.photo:hover img,.photo.active img{box-shadow:0 0 5px #005ecc}.photo:active{-webkit-transition-duration:.1s;-webkit-transform:scale(.98);-moz-transition-duration:.1s;-moz-transform:scale(.98);transition-duration:.1s;transform:scale(.98)}.album{float:left;display:inline-block;width:204px;height:204px;margin:30px 0 0 30px;cursor:pointer}.album img:first-child,.album img:nth-child(2){-webkit-transform:rotate(0)translateY(0)translateX(0);-moz-transform:rotate(0)translateY(0)translateX(0);transform:rotate(0)translateY(0)translateX(0);opacity:0}.album:hover img:first-child{-webkit-transform:rotate(-2deg)translateY(10px)translateX(-12px);-moz-transform:rotate(-2deg)translateY(10px)translateX(-12px);transform:rotate(-2deg)translateY(10px)translateX(-12px);opacity:1}.album:hover img:nth-child(2){-webkit-transform:rotate(5deg)translateY(-8px)translateX(12px);-moz-transform:rotate(5deg)translateY(-8px)translateX(12px);transform:rotate(5deg)translateY(-8px)translateX(12px);opacity:1}.album img{position:absolute;width:200px;height:200px;background-color:#222;border-radius:2px;border:2px solid #ccc}.album:hover img,.album.active img{box-shadow:0 0 5px #005ecc}.album .overlay,.photo .overlay{position:absolute;width:200px;height:200px;margin:2px}.album .overlay{background:-moz-linear-gradient(top,rgba(0,0,0,0)0%,rgba(0,0,0,0)20%,rgba(0,0,0,.9)100%);background:-webkit-linear-gradient(top,rgba(0,0,0,0)0%,rgba(0,0,0,0)20%,rgba(0,0,0,.9)100%);background:-ms-linear-gradient(top,rgba(0,0,0,0)0%,rgba(0,0,0,0)20%,rgba(0,0,0,.9)100%);background:linear-gradient(top,rgba(0,0,0,0)0%,rgba(0,0,0,0)20%,rgba(0,0,0,.9)100%)}.photo .overlay{background:-moz-linear-gradient(top,rgba(0,0,0,0)0%,rgba(0,0,0,0)60%,rgba(0,0,0,.5)80%,rgba(0,0,0,.9)100%);background:-webkit-linear-gradient(top,rgba(0,0,0,0)0%,rgba(0,0,0,0)60%,rgba(0,0,0,.5)80%,rgba(0,0,0,.9)100%);background:-ms-linear-gradient(top,rgba(0,0,0,0)0%,rgba(0,0,0,0)60%,rgba(0,0,0,.5)80%,rgba(0,0,0,.9)100%);background:linear-gradient(top,rgba(0,0,0,0)0%,rgba(0,0,0,0)60%,rgba(0,0,0,.5)80%,rgba(0,0,0,.9)100%);opacity:0}.photo:hover .overlay,.photo.active .overlay{opacity:1}.album .overlay h1,.photo .overlay h1{min-height:19px;width:190px;margin:153px 0 3px 15px;color:#fff;font-size:16px;font-weight:700;overflow:hidden}.album .overlay a,.photo .overlay a{font-size:11px;color:#aaa}.album .overlay a{margin-left:15px}.photo .overlay a{margin:155px 0 5px 15px}.album .badge,.photo .badge{position:absolute;margin-top:-1px;margin-left:12px;padding:12px 7px 3px;box-shadow:0 0 3px #000;border-radius:0 0 3px 3px;border:1px solid #fff;border-top:none;color:#fff;font-size:24px;text-shadow:0 1px 0 #000;opacity:.9}.album .badge.icon-star,.photo .badge.icon-star{padding:12px 8px 3px}.album .badge.icon-share,.photo .badge.icon-share{padding:12px 6px 3px 8px}.album .badge::after,.photo .badge::after{content:"";position:absolute;margin-top:-12px;margin-left:-26px;width:38px;height:5px;background:-moz-linear-gradient(top,rgba(0,0,0,1)0%,rgba(0,0,0,0)100%);background:-webkit-linear-gradient(top,rgba(0,0,0,1)0%,rgba(0,0,0,0)100%);background:-ms-linear-gradient(top,rgba(0,0,0,1)0%,rgba(0,0,0,0)100%);background:linear-gradient(top,rgba(0,0,0,1)0%,rgba(0,0,0,0)100%);opacity:.4}.album .badge.icon-star::after,.photo .badge.icon-star::after{margin-left:-29px}.album .badge.icon-share::after,.photo .badge.icon-share::after{margin-left:-31px}.album .badge.icon-reorder::after{margin-left:-30px}.album .badge:nth-child(2n),.photo .badge:nth-child(2n){margin-left:57px}.album .badge.red,.photo .badge.red{background:#d64b4b;background:-webkit-linear-gradient(top,#d64b4b,#ab2c2c);background:-moz-linear-gradient(top,#d64b4b,#ab2c2c);background:-ms-linear-gradient(top,#d64b4b,#ab2c2c)}.album .badge.blue,.photo .badge.blue{background:#d64b4b;background:-webkit-linear-gradient(top,#347cd6,#2945ab);background:-moz-linear-gradient(top,#347cd6,#2945ab);background:-ms-linear-gradient(top,#347cd6,#2945ab)}.divider{float:left;width:100%;margin-top:50px;opacity:0;border-top:1px solid #2E2E2E;box-shadow:0 -1px 0 #151515}.divider:first-child{margin-top:0;border-top:none}.divider h1{float:left;margin:20px 0 0 30px;color:#fff;font-size:14px;font-weight:700;text-shadow:0 -1px 0 #000}.no_content{position:absolute;top:50%;left:50%;height:160px;width:180px;margin-top:-80px;margin-left:-90px;padding-top:20px;color:rgba(20,20,20,1);text-shadow:0 1px 0 rgba(255,255,255,.05);text-align:center}.no_content .icon{font-size:120px}.no_content p{font-size:18px}.contextmenu_bg{position:fixed;height:100%;width:100%;z-index:1000}.contextmenu{position:fixed;top:0;left:0;padding:5px 0 6px;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,.7);border-bottom:1px solid rgba(0,0,0,.9);border-radius:5px;box-shadow:0 4px 5px rgba(0,0,0,.3),inset 0 1px 0 rgba(255,255,255,.15),inset 1px 0 0 rgba(255,255,255,.05),inset -1px 0 0 rgba(255,255,255,.05);opacity:0;z-index:1001;-webkit-transition:none;-moz-transition:none;transition:none}.contextmenu tr{font-size:14px;color:#eee;text-shadow:0 -1px 0 rgba(0,0,0,.6);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)}.contextmenu tr.no_hover:hover{cursor:inherit;background-color:inherit;background-image:none}.contextmenu tr.separator{float:left;height:1px;width:100%;background-color:#1c1c1c;border-bottom:1px solid #4a4a4a;margin:5px 0;cursor:inherit}.contextmenu tr.separator:hover{background-color:#222;background-image:none}.contextmenu tr td{padding:7px 30px 6px 12px;white-space:nowrap;-webkit-transition:none;-moz-transition:none;transition:none}.contextmenu tr:hover td{color:#fff;box-shadow:inset 0 1px 0 rgba(255,255,255,.05);text-shadow:0 -1px 0 rgba(0,0,0,.4)}.contextmenu tr.no_hover:hover td{box-shadow:none}.contextmenu tr a{float:left;width:10px;margin-right:10px;text-align:center}.contextmenu #link{float:right;width:140px;margin:0 -17px -1px 0;padding:4px 6px 5px;background-color:#444;color:#fff;border:1px solid #111;box-shadow:0 1px 0 rgba(255,255,255,.1);outline:none;border-radius:5px}.contextmenu tr a#link_icon{padding-top:4px}@font-face{font-family:'FontAwesome';src:url('../font/fontawesome-webfont.eot');src:url('../font/fontawesome-webfont.eot?#iefix') format('eot'),url('../font/fontawesome-webfont.woff') format('woff'),url('../font/fontawesome-webfont.ttf') format('truetype'),url('../font/fontawesome-webfont.svg#FontAwesome') format('svg');font-weight:400;font-style:normal}[class^="icon-"]:before,[class*=" icon-"]:before{font-family:FontAwesome;font-weight:400;font-style:normal;display:inline-block;text-decoration:inherit}a [class^="icon-"],a [class*=" icon-"]{display:inline-block;text-decoration:inherit}.icon-large:before{vertical-align:top;font-size:1.3333333333333333em}.btn [class^="icon-"],.btn [class*=" icon-"]{line-height:.9em}li [class^="icon-"],li [class*=" icon-"]{display:inline-block;width:1.25em;text-align:center}li .icon-large[class^="icon-"],li .icon-large[class*=" icon-"]{width:1.875em}li[class^="icon-"],li[class*=" icon-"]{margin-left:0;list-style-type:none}li[class^="icon-"]:before,li[class*=" icon-"]:before{text-indent:-2em;text-align:center}li[class^="icon-"].icon-large:before,li[class*=" icon-"].icon-large:before{text-indent:-1.3333333333333333em}.icon-glass:before{content:"\f000"}.icon-music:before{content:"\f001"}.icon-search:before{content:"\f002"}.icon-envelope:before{content:"\f003"}.icon-heart:before{content:"\f004"}.icon-star:before{content:"\f005"}.icon-star-empty:before{content:"\f006"}.icon-user:before{content:"\f007"}.icon-film:before{content:"\f008"}.icon-th-large:before{content:"\f009"}.icon-th:before{content:"\f00a"}.icon-th-list:before{content:"\f00b"}.icon-ok:before{content:"\f00c"}.icon-remove:before{content:"\f00d"}.icon-zoom-in:before{content:"\f00e"}.icon-zoom-out:before{content:"\f010"}.icon-off:before{content:"\f011"}.icon-signal:before{content:"\f012"}.icon-cog:before{content:"\f013"}.icon-trash:before{content:"\f014"}.icon-home:before{content:"\f015"}.icon-file:before{content:"\f016"}.icon-time:before{content:"\f017"}.icon-road:before{content:"\f018"}.icon-download-alt:before{content:"\f019"}.icon-download:before{content:"\f01a"}.icon-upload:before{content:"\f01b"}.icon-inbox:before{content:"\f01c"}.icon-play-circle:before{content:"\f01d"}.icon-repeat:before{content:"\f01e"}.icon-refresh:before{content:"\f021"}.icon-list-alt:before{content:"\f022"}.icon-lock:before{content:"\f023"}.icon-flag:before{content:"\f024"}.icon-headphones:before{content:"\f025"}.icon-volume-off:before{content:"\f026"}.icon-volume-down:before{content:"\f027"}.icon-volume-up:before{content:"\f028"}.icon-qrcode:before{content:"\f029"}.icon-barcode:before{content:"\f02a"}.icon-tag:before{content:"\f02b"}.icon-tags:before{content:"\f02c"}.icon-book:before{content:"\f02d"}.icon-bookmark:before{content:"\f02e"}.icon-print:before{content:"\f02f"}.icon-camera:before{content:"\f030"}.icon-font:before{content:"\f031"}.icon-bold:before{content:"\f032"}.icon-italic:before{content:"\f033"}.icon-text-height:before{content:"\f034"}.icon-text-width:before{content:"\f035"}.icon-align-left:before{content:"\f036"}.icon-align-center:before{content:"\f037"}.icon-align-right:before{content:"\f038"}.icon-align-justify:before{content:"\f039"}.icon-list:before{content:"\f03a"}.icon-indent-left:before{content:"\f03b"}.icon-indent-right:before{content:"\f03c"}.icon-facetime-video:before{content:"\f03d"}.icon-picture:before{content:"\f03e"}.icon-pencil:before{content:"\f040"}.icon-map-marker:before{content:"\f041"}.icon-adjust:before{content:"\f042"}.icon-tint:before{content:"\f043"}.icon-edit:before{content:"\f044"}.icon-share:before{content:"\f045"}.icon-check:before{content:"\f046"}.icon-move:before{content:"\f047"}.icon-step-backward:before{content:"\f048"}.icon-fast-backward:before{content:"\f049"}.icon-backward:before{content:"\f04a"}.icon-play:before{content:"\f04b"}.icon-pause:before{content:"\f04c"}.icon-stop:before{content:"\f04d"}.icon-forward:before{content:"\f04e"}.icon-fast-forward:before{content:"\f050"}.icon-step-forward:before{content:"\f051"}.icon-eject:before{content:"\f052"}.icon-chevron-left:before{content:"\f053"}.icon-chevron-right:before{content:"\f054"}.icon-plus-sign:before{content:"\f055"}.icon-minus-sign:before{content:"\f056"}.icon-remove-sign:before{content:"\f057"}.icon-ok-sign:before{content:"\f058"}.icon-question-sign:before{content:"\f059"}.icon-info-sign:before{content:"\f05a"}.icon-screenshot:before{content:"\f05b"}.icon-remove-circle:before{content:"\f05c"}.icon-ok-circle:before{content:"\f05d"}.icon-ban-circle:before{content:"\f05e"}.icon-arrow-left:before{content:"\f060"}.icon-arrow-right:before{content:"\f061"}.icon-arrow-up:before{content:"\f062"}.icon-arrow-down:before{content:"\f063"}.icon-share-alt:before{content:"\f064"}.icon-resize-full:before{content:"\f065"}.icon-resize-small:before{content:"\f066"}.icon-plus:before{content:"\f067"}.icon-minus:before{content:"\f068"}.icon-asterisk:before{content:"\f069"}.icon-exclamation-sign:before{content:"\f06a"}.icon-gift:before{content:"\f06b"}.icon-leaf:before{content:"\f06c"}.icon-fire:before{content:"\f06d"}.icon-eye-open:before{content:"\f06e"}.icon-eye-close:before{content:"\f070"}.icon-warning-sign:before{content:"\f071"}.icon-plane:before{content:"\f072"}.icon-calendar:before{content:"\f073"}.icon-random:before{content:"\f074"}.icon-comment:before{content:"\f075"}.icon-magnet:before{content:"\f076"}.icon-chevron-up:before{content:"\f077"}.icon-chevron-down:before{content:"\f078"}.icon-retweet:before{content:"\f079"}.icon-shopping-cart:before{content:"\f07a"}.icon-folder-close:before{content:"\f07b"}.icon-folder-open:before{content:"\f07c"}.icon-resize-vertical:before{content:"\f07d"}.icon-resize-horizontal:before{content:"\f07e"}.icon-bar-chart:before{content:"\f080"}.icon-twitter-sign:before{content:"\f081"}.icon-facebook-sign:before{content:"\f082"}.icon-camera-retro:before{content:"\f083"}.icon-key:before{content:"\f084"}.icon-cogs:before{content:"\f085"}.icon-comments:before{content:"\f086"}.icon-thumbs-up:before{content:"\f087"}.icon-thumbs-down:before{content:"\f088"}.icon-star-half:before{content:"\f089"}.icon-heart-empty:before{content:"\f08a"}.icon-signout:before{content:"\f08b"}.icon-linkedin-sign:before{content:"\f08c"}.icon-pushpin:before{content:"\f08d"}.icon-external-link:before{content:"\f08e"}.icon-signin:before{content:"\f090"}.icon-trophy:before{content:"\f091"}.icon-github-sign:before{content:"\f092"}.icon-upload-alt:before{content:"\f093"}.icon-lemon:before{content:"\f094"}.icon-phone:before{content:"\f095"}.icon-check-empty:before{content:"\f096"}.icon-bookmark-empty:before{content:"\f097"}.icon-phone-sign:before{content:"\f098"}.icon-twitter:before{content:"\f099"}.icon-facebook:before{content:"\f09a"}.icon-github:before{content:"\f09b"}.icon-unlock:before{content:"\f09c"}.icon-credit-card:before{content:"\f09d"}.icon-rss:before{content:"\f09e"}.icon-hdd:before{content:"\f0a0"}.icon-bullhorn:before{content:"\f0a1"}.icon-bell:before{content:"\f0a2"}.icon-certificate:before{content:"\f0a3"}.icon-hand-right:before{content:"\f0a4"}.icon-hand-left:before{content:"\f0a5"}.icon-hand-up:before{content:"\f0a6"}.icon-hand-down:before{content:"\f0a7"}.icon-circle-arrow-left:before{content:"\f0a8"}.icon-circle-arrow-right:before{content:"\f0a9"}.icon-circle-arrow-up:before{content:"\f0aa"}.icon-circle-arrow-down:before{content:"\f0ab"}.icon-globe:before{content:"\f0ac"}.icon-wrench:before{content:"\f0ad"}.icon-tasks:before{content:"\f0ae"}.icon-filter:before{content:"\f0b0"}.icon-briefcase:before{content:"\f0b1"}.icon-fullscreen:before{content:"\f0b2"}.icon-group:before{content:"\f0c0"}.icon-link:before{content:"\f0c1"}.icon-cloud:before{content:"\f0c2"}.icon-beaker:before{content:"\f0c3"}.icon-cut:before{content:"\f0c4"}.icon-copy:before{content:"\f0c5"}.icon-paper-clip:before{content:"\f0c6"}.icon-save:before{content:"\f0c7"}.icon-sign-blank:before{content:"\f0c8"}.icon-reorder:before{content:"\f0c9"}.icon-list-ul:before{content:"\f0ca"}.icon-list-ol:before{content:"\f0cb"}.icon-strikethrough:before{content:"\f0cc"}.icon-underline:before{content:"\f0cd"}.icon-table:before{content:"\f0ce"}.icon-magic:before{content:"\f0d0"}.icon-truck:before{content:"\f0d1"}.icon-pinterest:before{content:"\f0d2"}.icon-pinterest-sign:before{content:"\f0d3"}.icon-google-plus-sign:before{content:"\f0d4"}.icon-google-plus:before{content:"\f0d5"}.icon-money:before{content:"\f0d6"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.icon-columns:before{content:"\f0db"}.icon-sort:before{content:"\f0dc"}.icon-sort-down:before{content:"\f0dd"}.icon-sort-up:before{content:"\f0de"}.icon-envelope-alt:before{content:"\f0e0"}.icon-linkedin:before{content:"\f0e1"}.icon-undo:before{content:"\f0e2"}.icon-legal:before{content:"\f0e3"}.icon-dashboard:before{content:"\f0e4"}.icon-comment-alt:before{content:"\f0e5"}.icon-comments-alt:before{content:"\f0e6"}.icon-bolt:before{content:"\f0e7"}.icon-sitemap:before{content:"\f0e8"}.icon-umbrella:before{content:"\f0e9"}.icon-paste:before{content:"\f0ea"}.icon-user-md:before{content:"\f200"}header{position:fixed;height:49px;width:100%;background-image:-webkit-linear-gradient(top,#3E3E3E,#282828);background-image:-moz-linear-gradient(top,#3E3E3E,#282828);background-image:-ms-linear-gradient(top,#3E3E3E,#282828);background-image:linear-gradient(top,#3E3E3E,#282828);border-bottom:1px solid #161616;z-index:1;-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;transition:transform .3s ease-out}header.hidden{-webkit-transform:translateY(-60px);-moz-transform:translateY(-60px);transform:translateY(-60px)}header.loading{-webkit-transform:translateY(2px);-moz-transform:translateY(2px);transform:translateY(2px)}header.error{-webkit-transform:translateY(40px);-moz-transform:translateY(40px);transform:translateY(40px)}header.view.error{background-color:rgba(10,10,10,.99)}header.view{background-image:none;border-bottom:none}header.view .button,header.view #title,header.view .tools{text-shadow:none!important}header #title{position:absolute;margin:0 30%;width:40%;padding:15px 0;color:#fff;font-size:16px;font-weight:700;text-align:center;text-shadow:0 -1px 0 #222}header #title.editable{cursor:pointer}header .button{color:#888;font-family:'FontAwesome';font-size:21px;font-weight:700;text-decoration:none!important;cursor:pointer;text-shadow:0 -1px 0 #222}header .button.left{float:left;position:absolute;padding:16px 10px 8px 18px}header .button.right{float:right;position:relative;padding:16px 19px 13px 11px}header .button:hover{color:#fff}header #tools_albums,header #tools_album,header #tools_photo,header #button_signin{display:none}header .button_divider{float:right;position:relative;width:14px;height:50px}header #search{float:right;width:80px;margin:12px 12px 0 0;padding:5px 12px 6px;background-color:#383838;color:#fff;border:1px solid #131313;box-shadow:0 1px 0 rgba(255,255,255,.1);outline:none;border-radius:50px;opacity:.6;-webkit-transition:opacity .3s ease-out,-webkit-transform .3s ease-out,box-shadow .3s,width .2s ease-out;-moz-transition:opacity .3s ease-out,-moz-transform .3s ease-out,box-shadow .3s,width .2s ease-out;transition:opacity .3s ease-out,transform .3s ease-out,box-shadow .3s,width .2s ease-out}header #search:focus{width:140px}header #search:focus~#clearSearch{opacity:1}header #clearSearch{position:absolute;top:15px;right:81px;padding:0;font-size:20px;opacity:0;-webkit-transition:opacity .2s ease-out;-moz-transition:opacity .2s ease-out;transition:opacity .2s ease-out}header #clearSearch:hover{opacity:1}header .tools:first-of-type{margin-right:6px}header .tools{float:right;padding:14px 8px;color:#888;font-size:21px;text-shadow:0 -1px 0 #222;cursor:pointer}header .tools:hover a{color:#fff}header .tools .icon-star{color:#f0ef77}header .tools .icon-share.active{color:#ff9737}header #hostedwith{float:right;padding:5px 10px;margin:13px 9px;color:#888;font-size:13px;text-shadow:0 -1px 0 #222;display:none;cursor:pointer}header #hostedwith:hover{background-color:rgba(0,0,0,.2);border-radius:100px}#imageview{position:fixed;display:none;width:100%;min-height:100%;background-color:rgba(10,10,10,.99);-webkit-transition:background-color .3s}#imageview.view{background-color:inherit}#imageview.full{background-color:#040404}#imageview #image{position:absolute;top:60px;right:30px;bottom:30px;left:30px;background-repeat:no-repeat;background-position:50% 50%;background-size:contain;-webkit-transition:top .3s,bottom .3s,margin-top .3s;-webkit-animation-name:zoomIn;-webkit-animation-duration:.3s;-webkit-animation-timing-function:cubic-bezier(.51,.92,.24,1.15);-moz-animation-name:zoomIn;-moz-animation-duration:.3s;-moz-animation-timing-function:cubic-bezier(.51,.92,.24,1.15);animation-name:zoomIn;animation-duration:.3s;animation-timing-function:cubic-bezier(.51,.92,.24,1.15)}#imageview #image.small{top:50%;right:auto;bottom:auto;left:50%}#imageview .arrow_wrapper{position:fixed;width:20%;height:calc(100% - 60px);top:60px;z-index:1}#imageview .arrow_wrapper.previous{left:0}#imageview .arrow_wrapper.next{right:0}#imageview .arrow_wrapper a{position:fixed;top:50%;margin-top:-10px;color:#fff;font-size:50px;text-shadow:0 1px 2px #000;cursor:pointer;opacity:0;z-index:2;-webkit-transition:opacity .2s;-moz-transition:opacity .2s;transition:opacity .2s}#imageview .arrow_wrapper:hover a{opacity:.2}#imageview .arrow_wrapper a#previous{left:20px}#imageview .arrow_wrapper a#next{right:20px}#infobox_overlay{z-index:3;position:fixed;width:100%;height:100%;top:0;left:0;background-color:rgba(0,0,0,.85)}#infobox{z-index:4;position:fixed;right:0;width:350px;height:100%;background-color:rgba(20,20,20,.98);box-shadow:-1px 0 2px rgba(0,0,0,.8);display:none;-webkit-transform:translateX(370px);-moz-transform:translateX(370px);transform:translateX(370px);-webkit-transition:-webkit-transform .3s cubic-bezier(.51,.92,.24,1.15);-moz-transition:-moz-transform .3s cubic-bezier(.51,.92,.24,1.15);transition:transform .3s cubic-bezier(.51,.92,.24,1.15)}#infobox.active{-webkit-transform:translateX(50px);-moz-transform:translateX(50px);transform:translateX(50px)}#infobox .wrapper{float:left;height:100%;width:300px;overflow:scroll}#infobox .edit{display:inline;margin-left:3px;width:20px;height:5px;cursor:pointer}#infobox .bumper{float:left;width:100%;height:50px}#infobox .header{float:left;height:49px;width:100%;background-color:#1d1d1d;background-image:-webkit-linear-gradient(top,#2A2A2A,#131313);background-image:-moz-linear-gradient(top,#2A2A2A,#131313);background-image:-ms-linear-gradient(top,#2A2A2A,#131313);background-image:linear-gradient(top,#2A2A2A,#131313);border-bottom:1px solid #000}#infobox .header h1{position:absolute;margin:15px 30% 15px calc(30% - 25px);width:40%;font-size:16px;text-align:center}#infobox .header h1,#infobox .header a{color:#fff;font-weight:700;text-shadow:0 -1px 0 #000}#infobox .header a{float:right;padding:15px 65px 15px 15px;font-size:20px;opacity:.5;cursor:pointer}#infobox .header a:hover{opacity:1}#infobox .separator{float:left;width:100%;border-top:1px solid rgba(255,255,255,.04);box-shadow:0 -1px 0 #000}#infobox .separator h1{margin:20px 0 5px 20px;color:#fff;font-size:14px;font-weight:700;text-shadow:0 -1px 0 #000}#infobox table{float:left;margin:10px 0 15px 20px}#infobox table tr td{padding:5px 0;color:#fff;font-size:14px;line-height:19px;-webkit-user-select:text;-moz-user-select:text;user-select:text}#infobox table tr td:first-child{width:110px}#infobox table tr td:last-child{padding-right:10px}#infobox #tags{width:calc(100% - 40px);margin:16px 20px 12px;color:#fff;display:inline-block}#infobox #tags .empty{font-size:14px;margin-bottom:8px}#infobox #tags .edit{display:inline-block}#infobox #tags .empty .edit{display:inline}#infobox .tag{float:left;padding:4px 7px;margin:0 6px 8px 0;background-color:rgba(0,0,0,.5);border:2px solid rgba(255,255,255,.3);border-radius:100px;font-size:12px;-webkit-transition:border .3s;-moz-transition:border .3s;transition:border .3s}#infobox .tag:hover{border:2px solid #aaa}#infobox .tag span{float:right;width:0;padding:0;margin:0 0 -2px;color:red;font-size:11px;cursor:pointer;overflow:hidden;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:width .3s,margin .3s,-webkit-transform .3s;-moz-transition:width .3s,margin .3s;transition:width .3s,margin .3s,transform .3s}#infobox .tag:hover span{width:10px;margin:0 0 -2px 6px;-webkit-transform:scale(1);transform:scale(1)}#loading{position:fixed;width:100%;height:3px;background-size:100px 3px;background-repeat:repeat-x;border-bottom:1px solid rgba(0,0,0,.3);display:none;-webkit-animation-name:moveBackground;-webkit-animation-duration:.3s;-webkit-animation-iteration-count:infinite;-webkit-animation-timing-function:linear;-moz-animation-name:moveBackground;-moz-animation-duration:.3s;-moz-animation-iteration-count:infinite;-moz-animation-timing-function:linear;animation-name:moveBackground;animation-duration:.3s;animation-iteration-count:infinite;animation-timing-function:linear}#loading.loading{background-image:-webkit-linear-gradient(left,#153674 0%,#153674 47%,#2651AE 53%,#2651AE 100%);background-image:-moz-linear-gradient(left,#153674 0%,#153674 47%,#2651AE 53%,#2651AE 100%);background-image:linear-gradient(left right,#153674 0%,#153674 47%,#2651AE 53%,#2651AE 100%);z-index:2}#loading.error{background-color:#2f0d0e;background-image:-webkit-linear-gradient(left,#451317 0%,#451317 47%,#AA3039 53%,#AA3039 100%);background-image:-moz-linear-gradient(left,#451317 0%,#451317 47%,#AA3039 53%,#AA3039 100%);background-image:linear-gradient(left right,#451317 0%,#451317 47%,#AA3039 53%,#AA3039 100%);z-index:1}#loading h1{margin:13px;color:#ddd;font-size:14px;font-weight:700;text-shadow:0 1px 0 #000;text-transform:capitalize}#loading h1 span{margin-left:10px;font-weight:400;text-transform:none}@media only screen and (max-width:900px){#title{width:40%!important}#title,#title.view{margin:0 20%!important}#title.view{width:60%!important}#title span{display:none!important}}@media only screen and (max-width:640px){#title{display:none!important}#title.view{display:block!important;width:70%!important;margin:0 20% 0 10%!important}#button_move,#button_archive{display:none!important}.center{top:0!important;left:0!important}.album,.photo{margin:40px 0 0 50px!important}.message{position:fixed!important;width:100%!important;height:100%!important;margin:1px 0 0!important;border-radius:0!important;-webkit-animation:moveUp .3s!important;-moz-animation:moveUp .3s!important;animation:moveUp .3s!important}.upload_message{top:50%!important;left:50%!important}}.message_overlay{position:fixed;width:100%;height:100%;top:0;left:0;background-color:rgba(0,0,0,.85);z-index:1000}.message{position:absolute;display:inline-block;width:500px;margin-left:-250px;margin-top:-95px;background-color:#444;background-image:-webkit-linear-gradient(top,#4b4b4b ,#2d2d2d);background-image:-moz-linear-gradient(top,#4b4b4b ,#2d2d2d);background-image:-ms-linear-gradient(top,#4b4b4b ,#2d2d2d);background-image:linear-gradient(top,#4b4b4b ,#2d2d2d);border-radius:5px;box-shadow:0 0 5px #000,inset 0 1px 0 rgba(255,255,255,.08),inset 1px 0 0 rgba(255,255,255,.03),inset -1px 0 0 rgba(255,255,255,.03);-webkit-animation-name:moveUp;-webkit-animation-duration:.3s;-webkit-animation-timing-function:cubic-bezier(.51,.92,.24,1.15);-moz-animation-name:moveUp;-moz-animation-duration:.3s;-moz-animation-timing-function:cubic-bezier(.51,.92,.24,1.15);animation-name:moveUp;animation-duration:.3s;animation-timing-function:cubic-bezier(.51,.92,.24,1.15)}.message h1{float:left;width:100%;padding:12px 0;color:#fff;font-size:16px;font-weight:700;text-shadow:0 -1px 0 #222;text-align:center}.message .close{position:absolute;top:0;right:0;padding:12px 14px 6px 7px;color:#aaa;font-size:20px;text-shadow:0 -1px 0 #222;cursor:pointer}.message .close:hover{color:#fff}.message p{float:left;width:90%;margin-top:1px;padding:12px 5% 15px;color:#eee;font-size:14px;text-shadow:0 -1px 0 #222;line-height:20px}.message p b{font-weight:700}.message p a{color:#eee;text-decoration:none;border-bottom:1px dashed #888}.message .button{float:right;margin:15px 15px 15px 0;padding:6px 10px 8px;background-color:#4e4e4e;background-image:-webkit-linear-gradient(top,#3c3c3c ,#2d2d2d);background-image:-moz-linear-gradient(top,#3c3c3c ,#2d2d2d);background-image:-ms-linear-gradient(top,#3c3c3c ,#2d2d2d);background-image:linear-gradient(top,#3c3c3c ,#2d2d2d);color:#ccc;font-size:14px;font-weight:700;text-align:center;text-shadow:0 -1px 0 #222;border-radius:5px;border:1px solid #191919;box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);cursor:pointer}.message .button:first-of-type{margin:15px 5% 18px 0!important}.message .button.active{color:#fff;box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1),0 0 4px #005ecc}.message .button:hover{background-color:#565757;background-image:-webkit-linear-gradient(top,#505050 ,#393939);background-image:-moz-linear-gradient(top,#505050 ,#393939);background-image:-ms-linear-gradient(top,#505050 ,#393939);background-image:linear-gradient(top,#505050 ,#393939)}.message .button:active,.message .button.pressed{background-color:#393939;background-image:-webkit-linear-gradient(top,#393939 ,#464646);background-image:-moz-linear-gradient(top,#393939 ,#464646);background-image:-ms-linear-gradient(top,#393939 ,#464646);background-image:linear-gradient(top,#393939 ,#464646)}.sign_in{width:100%;margin-top:1px;padding:5px 0;color:#eee;font-size:14px;line-height:20px}.sign_in,.sign_in input{float:left;text-shadow:0 -1px 0 #222}.sign_in input{width:88%;padding:7px 1% 9px;margin:0 5%;background-color:transparent;color:#fff;border:none;border-bottom:1px solid #222;box-shadow:0 1px 0 rgba(255,255,255,.1);border-radius:0;outline:none}.sign_in input:first-of-type{margin-bottom:10px}.sign_in input.error:focus{box-shadow:0 1px 0 rgba(204,0,7,.6)}.message #version{display:inline-block;margin-top:23px;margin-left:5%;color:#888;text-shadow:0 -1px 0 #111}.message #version span{display:none}.message #version span a{color:#888}.message input.text{float:left;width:calc(100% - 10px);padding:17px 5px 9px;margin-top:10px;background-color:transparent;color:#fff;text-shadow:0 -1px 0 #222;border:none;box-shadow:0 1px 0 rgba(255,255,255,.1);border-bottom:1px solid #222;border-radius:0;outline:none}.message input.less{margin-bottom:-10px}.message input.more{margin-bottom:30px}.message .copylink{margin-bottom:20px}html,body{min-height:100%}body{background-color:#222;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:12px;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased}body.view{background-color:#0f0f0f}.center{position:absolute;left:50%;top:50%}*{-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-transition:color .3s,opacity .3s ease-out,-webkit-transform .3s ease-out,box-shadow .3s;-moz-transition:opacity .3s ease-out,-moz-transform .3s ease-out,box-shadow .3s;transition:color .3s,opacity .3s ease-out,transform .3s ease-out,box-shadow .3s}input{-webkit-user-select:text!important;-moz-user-select:text!important;user-select:text!important}#multiselect{position:absolute;background-color:rgba(0,94,204,.3);border:1px solid rgba(0,94,204,1);border-radius:3px;z-index:3}.tipsy{padding:4px;font-size:12px;position:absolute;z-index:100000;-webkit-animation-name:fadeIn;-webkit-animation-duration:.3s;-moz-animation-name:fadeIn;-moz-animation-duration:.3s;animation-name:fadeIn;animation-duration:.3s}.tipsy-inner{padding:8px 10px 7px;color:#fff;max-width:200px;text-align:center;background:rgba(0,0,0,.8);border-radius:25px}.tipsy-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed rgba(0,0,0,.8)}.tipsy-arrow-n{border-bottom-color:rgba(0,0,0,.8)}.tipsy-arrow-s{border-top-color:rgba(0,0,0,.8)}.tipsy-arrow-e{border-left-color:rgba(0,0,0,.8)}.tipsy-arrow-w{border-right-color:rgba(0,0,0,.8)}.tipsy-n .tipsy-arrow{left:50%;margin-left:-5px}.tipsy-n .tipsy-arrow,.tipsy-nw .tipsy-arrow{top:0;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.tipsy-nw .tipsy-arrow{left:10px}.tipsy-ne .tipsy-arrow{top:0;right:10px;border-bottom-style:solid;border-top:none}.tipsy-ne .tipsy-arrow,.tipsy-s .tipsy-arrow{border-left-color:transparent;border-right-color:transparent}.tipsy-s .tipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:none}.tipsy-sw .tipsy-arrow{left:10px}.tipsy-sw .tipsy-arrow,.tipsy-se .tipsy-arrow{bottom:0;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.tipsy-se .tipsy-arrow{right:10px}.tipsy-e .tipsy-arrow{right:0;border-left-style:solid;border-right:none}.tipsy-e .tipsy-arrow,.tipsy-w .tipsy-arrow{top:50%;margin-top:-5px;border-top-color:transparent;border-bottom-color:transparent}.tipsy-w .tipsy-arrow{left:0;border-right-style:solid;border-left:none}#upload{display:none}.upload_overlay{position:fixed;width:100%;height:100%;top:0;left:0;background-color:rgba(0,0,0,.85);z-index:1000}.upload_message{position:absolute;display:inline-block;width:200px;margin-left:-100px;margin-top:-85px;background-color:#444;background-image:-webkit-linear-gradient(top,#4b4b4b ,#2d2d2d);background-image:-moz-linear-gradient(top,#4b4b4b ,#2d2d2d);background-image:-ms-linear-gradient(top,#4b4b4b ,#2d2d2d);background-image:linear-gradient(top,#4b4b4b ,#2d2d2d);border-radius:5px;box-shadow:0 0 5px #000,inset 0 1px 0 rgba(255,255,255,.08),inset 1px 0 0 rgba(255,255,255,.03),inset -1px 0 0 rgba(255,255,255,.03);-webkit-animation-name:moveUp;-webkit-animation-duration:.3s;-webkit-animation-timing-function:cubic-bezier(.51,.92,.24,1.15);-moz-animation-name:moveUp;-moz-animation-duration:.3s;-moz-animation-timing-function:cubic-bezier(.51,.92,.24,1.15);animation-name:moveUp;animation-duration:.3s;animation-timing-function:cubic-bezier(.51,.92,.24,1.15)}.upload_message a{margin:35px 0 5px;font-size:70px;text-shadow:0 1px 2px rgba(0,0,0,.5);-webkit-animation-name:pulse;-webkit-animation-duration:2s;-webkit-animation-timing-function:ease-in-out;-webkit-animation-iteration-count:infinite;-moz-animation-name:pulse;-moz-animation-duration:2s;-moz-animation-timing-function:ease-in-out;-moz-animation-iteration-count:infinite;animation-name:pulse;animation-duration:2s;animation-timing-function:ease-in-out;animation-iteration-count:infinite}.upload_message a,.upload_message p{float:left;width:100%;color:#fff;text-align:center}.upload_message p{margin:10px 0 35px;font-size:14px;text-shadow:0 -1px 0 rgba(0,0,0,.5)}.upload_message .progressbar{float:left;width:170px;height:25px;margin:15px;background-size:50px 25px;background-repeat:repeat-x;background-image:-webkit-linear-gradient(left,#191919 0%,#191919 47%,#1D1D1D 53%,#1D1D1D 100%);background-image:-moz-linear-gradient(left,#191919 0%,#191919 47%,#1D1D1D 53%,#1D1D1D 100%);background-image:linear-gradient(left right,#191919 0%,#191919 47%,#1D1D1D 53%,#1D1D1D 100%);border:1px solid #090909;box-shadow:0 1px 0 rgba(255,255,255,.06),inset 0 0 2px #222;border-radius:50px;-webkit-animation-name:moveBackground;-webkit-animation-duration:1s;-webkit-animation-timing-function:linear;-webkit-animation-iteration-count:infinite;-moz-animation-name:moveBackground;-moz-animation-duration:1s;-moz-animation-timing-function:linear;-moz-animation-iteration-count:infinite;animation-name:moveBackground;animation-duration:1s;animation-timing-function:linear;animation-iteration-count:infinite}.upload_message .progressbar div{float:left;width:0%;height:100%;box-shadow:0 1px 0 #000,1px 0 2px #000;background-color:#f5f2f7;background-image:-webkit-linear-gradient(top,#f5f2f7,#c7c6c8);background-image:-moz-linear-gradient(top,#f5f2f7,#c7c6c8);background-image:-ms-linear-gradient(top,#f5f2f7,#c7c6c8);background-image:linear-gradient(top,#f5f2f7,#c7c6c8);border-radius:50px;-webkit-transition:width .2s,opacity .5;-moz-transition:width .2s,opacity .5;transition:width .2s,opacity .5}
\ No newline at end of file
diff --git a/assets/build/main.js b/assets/build/main.js
deleted file mode 100644
index d3ba2b3..0000000
--- a/assets/build/main.js
+++ /dev/null
@@ -1,6 +0,0 @@
-(function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],p="2.0.3",f=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:p,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return f.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,p,f,h,d,g,m,y,v="sizzle"+-new Date,b=e.document,w=0,T=0,C=st(),k=st(),N=st(),E=!1,S=function(e,t){return e===t?(E=!0,0):0},j=typeof undefined,D=1<<31,A={}.hasOwnProperty,L=[],q=L.pop,H=L.push,O=L.push,F=L.slice,P=L.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",W="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",$=W.replace("w","w#"),B="\\["+M+"*("+W+")"+M+"*(?:([*^$|!~]?=)"+M+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+$+")|)|)"+M+"*\\]",I=":("+W+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+B.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=RegExp("^"+M+"*,"+M+"*"),X=RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=RegExp(M+"*[+~]"),Y=RegExp("="+M+"*([^\\]'\"]*)"+M+"*\\]","g"),V=RegExp(I),G=RegExp("^"+$+"$"),J={ID:RegExp("^#("+W+")"),CLASS:RegExp("^\\.("+W+")"),TAG:RegExp("^("+W.replace("w","w*")+")"),ATTR:RegExp("^"+B),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:RegExp("^(?:"+R+")$","i"),needsContext:RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Q=/^[^{]+\{\s*\[native \w/,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Z=/^(?:input|select|textarea|button)$/i,et=/^h\d$/i,tt=/'|\\/g,nt=RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),rt=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{O.apply(L=F.call(b.childNodes),b.childNodes),L[b.childNodes.length].nodeType}catch(it){O={apply:L.length?function(e,t){H.apply(e,F.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function ot(e,t,r,i){var o,s,a,u,l,f,g,m,x,w;if((t?t.ownerDocument||t:b)!==p&&c(t),t=t||p,r=r||[],!e||"string"!=typeof e)return r;if(1!==(u=t.nodeType)&&9!==u)return[];if(h&&!i){if(o=K.exec(e))if(a=o[1]){if(9===u){if(s=t.getElementById(a),!s||!s.parentNode)return r;if(s.id===a)return r.push(s),r}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(a))&&y(t,s)&&s.id===a)return r.push(s),r}else{if(o[2])return O.apply(r,t.getElementsByTagName(e)),r;if((a=o[3])&&n.getElementsByClassName&&t.getElementsByClassName)return O.apply(r,t.getElementsByClassName(a)),r}if(n.qsa&&(!d||!d.test(e))){if(m=g=v,x=t,w=9===u&&e,1===u&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(g=t.getAttribute("id"))?m=g.replace(tt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",l=f.length;while(l--)f[l]=m+mt(f[l]);x=U.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return O.apply(r,x.querySelectorAll(w)),r}catch(T){}finally{g||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,r,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>i.cacheLength&&delete t[e.shift()],t[n]=r}return t}function at(e){return e[v]=!0,e}function ut(e){var t=p.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function lt(e,t){var n=e.split("|"),r=e.length;while(r--)i.attrHandle[n[r]]=t}function ct(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return at(function(t){return t=+t,at(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}s=ot.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},n=ot.support={},c=ot.setDocument=function(e){var t=e?e.ownerDocument||e:b,r=t.defaultView;return t!==p&&9===t.nodeType&&t.documentElement?(p=t,f=t.documentElement,h=!s(t),r&&r.attachEvent&&r!==r.top&&r.attachEvent("onbeforeunload",function(){c()}),n.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ut(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=ut(function(e){return e.innerHTML="
",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),n.getById=ut(function(e){return f.appendChild(e).id=v,!t.getElementsByName||!t.getElementsByName(v).length}),n.getById?(i.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){return e.getAttribute("id")===t}}):(delete i.find.ID,i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=n.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.CLASS=n.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&h?t.getElementsByClassName(e):undefined},g=[],d=[],(n.qsa=Q.test(t.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll(":checked").length||d.push(":checked")}),ut(function(e){var n=t.createElement("input");n.setAttribute("type","hidden"),e.appendChild(n).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&d.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||d.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),d.push(",.*:")})),(n.matchesSelector=Q.test(m=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&ut(function(e){n.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",I)}),d=d.length&&RegExp(d.join("|")),g=g.length&&RegExp(g.join("|")),y=Q.test(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,r){if(e===r)return E=!0,0;var i=r.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(r);return i?1&i||!n.sortDetached&&r.compareDocumentPosition(e)===i?e===t||y(b,e)?-1:r===t||y(b,r)?1:l?P.call(l,e)-P.call(l,r):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],u=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:l?P.call(l,e)-P.call(l,n):0;if(o===s)return ct(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)u.unshift(r);while(a[i]===u[i])i++;return i?ct(a[i],u[i]):a[i]===b?-1:u[i]===b?1:0},t):p},ot.matches=function(e,t){return ot(e,null,null,t)},ot.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Y,"='$1']"),!(!n.matchesSelector||!h||g&&g.test(t)||d&&d.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return ot(t,p,null,[e]).length>0},ot.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},ot.attr=function(e,t){(e.ownerDocument||e)!==p&&c(e);var r=i.attrHandle[t.toLowerCase()],o=r&&A.call(i.attrHandle,t.toLowerCase())?r(e,t,!h):undefined;return o===undefined?n.attributes||!h?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null:o},ot.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ot.uniqueSort=function(e){var t,r=[],i=0,o=0;if(E=!n.detectDuplicates,l=!n.sortStable&&e.slice(0),e.sort(S),E){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return e},o=ot.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=ot.selectors={cacheLength:50,createPseudo:at,match:J,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(nt,rt),e[3]=(e[4]||e[5]||"").replace(nt,rt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ot.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ot.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return J.CHILD.test(e[0])?null:(e[3]&&e[4]!==undefined?e[2]=e[4]:n&&V.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(nt,rt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ot.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,y=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){p=t;while(p=p[g])if(a?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[v]||(m[v]={}),l=c[e]||[],h=l[0]===w&&l[1],f=l[0]===w&&l[2],p=h&&m.childNodes[h];while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[w,h,f];break}}else if(x&&(l=(t[v]||(t[v]={}))[e])&&l[0]===w)f=l[1];else while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if((a?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(x&&((p[v]||(p[v]={}))[e]=[w,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||ot.error("unsupported pseudo: "+e);return r[v]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?at(function(e,n){var i,o=r(e,t),s=o.length;while(s--)i=P.call(e,o[s]),e[i]=!(n[i]=o[s])}):function(e){return r(e,0,n)}):r}},pseudos:{not:at(function(e){var t=[],n=[],r=a(e.replace(z,"$1"));return r[v]?at(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:at(function(e){return function(t){return ot(e,t).length>0}}),contains:at(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:at(function(e){return G.test(e||"")||ot.error("unsupported lang: "+e),e=e.replace(nt,rt).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return et.test(e.nodeName)},input:function(e){return Z.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},i.pseudos.nth=i.pseudos.eq;for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})i.pseudos[t]=ft(t);function dt(){}dt.prototype=i.filters=i.pseudos,i.setFilters=new dt;function gt(e,t){var n,r,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=i.preFilter;while(a){(!n||(r=_.exec(a)))&&(r&&(a=a.slice(r[0].length)||a),u.push(o=[])),n=!1,(r=X.exec(a))&&(n=r.shift(),o.push({value:n,type:r[0].replace(z," ")}),a=a.slice(n.length));for(s in i.filter)!(r=J[s].exec(a))||l[s]&&!(r=l[s](r))||(n=r.shift(),o.push({value:n,type:s,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ot.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,n){var i=t.dir,o=n&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,a){var u,l,c,p=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[v]||(t[v]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,a)||r,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[v]&&(r=bt(r)),i&&!i[v]&&(i=bt(i,o)),at(function(o,s,a,u){var l,c,p,f=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,f,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(p=l[c])&&(y[h[c]]=!(m[h[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?P.call(o,p):f[c])>-1&&(o[l]=!(s[l]=p))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):O.apply(s,y)})}function wt(e){var t,n,r,o=e.length,s=i.relative[e[0].type],a=s||i.relative[" "],l=s?1:0,c=yt(function(e){return e===t},a,!0),p=yt(function(e){return P.call(t,e)>-1},a,!0),f=[function(e,n,r){return!s&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>l;l++)if(n=i.relative[e[l].type])f=[yt(vt(f),n)];else{if(n=i.filter[e[l].type].apply(null,e[l].matches),n[v]){for(r=++l;o>r;r++)if(i.relative[e[r].type])break;return bt(l>1&&vt(f),l>1&&mt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&wt(e.slice(l,r)),o>r&&wt(e=e.slice(r)),o>r&&mt(e))}f.push(n)}return vt(f)}function Tt(e,t){var n=0,o=t.length>0,s=e.length>0,a=function(a,l,c,f,h){var d,g,m,y=[],v=0,x="0",b=a&&[],T=null!=h,C=u,k=a||s&&i.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(u=l!==p&&l,r=n);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,c)){f.push(d);break}T&&(w=N,r=++n)}o&&((d=!m&&d)&&v--,a&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,c);if(a){if(v>0)while(x--)b[x]||y[x]||(y[x]=q.call(f));y=xt(y)}O.apply(f,y),T&&!a&&y.length>0&&v+t.length>1&&ot.uniqueSort(f)}return T&&(w=N,u=C),b};return o?at(a):a}a=ot.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[v]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ot(e,t[r],n);return n}function kt(e,t,r,o){var s,u,l,c,p,f=gt(e);if(!o&&1===f.length){if(u=f[0]=f[0].slice(0),u.length>2&&"ID"===(l=u[0]).type&&n.getById&&9===t.nodeType&&h&&i.relative[u[1].type]){if(t=(i.find.ID(l.matches[0].replace(nt,rt),t)||[])[0],!t)return r;e=e.slice(u.shift().value.length)}s=J.needsContext.test(e)?0:u.length;while(s--){if(l=u[s],i.relative[c=l.type])break;if((p=i.find[c])&&(o=p(l.matches[0].replace(nt,rt),U.test(u[0].type)&&t.parentNode||t))){if(u.splice(s,1),e=o.length&&mt(u),!e)return O.apply(r,o),r;break}}}return a(e,f)(o,t,!h,r,U.test(e)),r}n.sortStable=v.split("").sort(S).join("")===v,n.detectDuplicates=E,c(),n.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(p.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||lt("type|href|height|width",function(e,t,n){return n?undefined:e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||lt("value",function(e,t,n){return n||"input"!==e.nodeName.toLowerCase()?undefined:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||lt(R,function(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}),x.find=ot,x.expr=ot.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ot.uniqueSort,x.text=ot.getText,x.isXMLDoc=ot.isXML,x.contains=ot.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(p){for(t=e.memory&&p,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(p[0],p[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!a||n&&!u||(t=t||[],t=[e,t.slice?t.slice():t],r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))x.extend(this.cache[i],t);else for(r in t)o[r]=t[r];return o},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){var r;return t===undefined||t&&"string"==typeof t&&n===undefined?(r=this.get(e,t),r!==undefined?r:this.get(e,x.camelCase(t))):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i,o=this.key(e),s=this.cache[o];if(t===undefined)this.cache[o]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):(i=x.camelCase(t),t in s?r=[t,i]:(r=i,r=r in s?[r]:r.match(w)||[])),n=r.length;while(n--)delete s[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){e[this.expando]&&delete this.cache[e[this.expando]]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.slice(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t)
-};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t);x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n\f]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,i=0,o=x(this),s=e.match(w)||[];while(t=s[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,x(this).val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.bool.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,p,f,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(f=x.event.special[d]||{},d=(o?f.delegateType:f.bindType)||d,f=x.event.special[d]||{},p=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,f.setup&&f.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),f.add&&(f.add.call(e,p),p.handler.guid||(p.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,p):h.push(p),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,p,f,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){p=x.event.special[h]||{},h=(r?p.delegateType:p.bindType)||h,f=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=f.length;while(o--)c=f[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(f.splice(o,1),c.selector&&f.delegateCount--,p.remove&&p.remove.call(e,c));s&&!f.length&&(p.teardown&&p.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,p,f,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),f=x.event.special[d]||{},i||!f.trigger||f.trigger.apply(r,n)!==!1)){if(!i&&!f.noBubble&&!x.isWindow(r)){for(l=f.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:f.bindType||d,p=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),p&&p.apply(a,n),p=c&&a[c],p&&x.acceptData(a)&&p.apply&&p.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||f._default&&f._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,s=e,a=this.fixHooks[i];a||(this.fixHooks[i]=a=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=a.props?this.props.concat(a.props):this.props,e=new x.Event(s),t=r.length;while(t--)n=r[t],e[n]=s[n];return e.target||(e.target=o),3===e.target.nodeType&&(e.target=e.target.parentNode),a.filter?a.filter(e,s):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=/^(?:parents|prev(?:Until|All))/,Q=x.expr.match.needsContext,K={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(et(this,e||[],!0))},filter:function(e){return this.pushStack(et(this,e||[],!1))},is:function(e){return!!et(this,"string"==typeof e&&Q.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],s=Q.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function Z(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return Z(e,"nextSibling")},prev:function(e){return Z(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return e.contentDocument||x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(K[e]||x.unique(i),J.test(e)&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function et(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,nt=/<([\w:]+)/,rt=/<|?\w+;/,it=/<(?:script|style|link)/i,ot=/^(?:checkbox|radio)$/i,st=/checked\s*(?:[^=]|=\s*.checked.)/i,at=/^$|\/(?:java|ecma)script/i,ut=/^true\/(.*)/,lt=/^\s*\s*$/g,ct={option:[1,""],thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};ct.optgroup=ct.option,ct.tbody=ct.tfoot=ct.colgroup=ct.caption=ct.thead,ct.th=ct.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(mt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&dt(mt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(mt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!it.test(e)&&!ct[(nt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(tt,"<$1>$2>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(mt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=f.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,p=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&st.test(d))return this.each(function(r){var i=p.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(mt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,mt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,ht),l=0;s>l;l++)a=o[l],at.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(lt,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=mt(a),o=mt(e),r=0,i=o.length;i>r;r++)yt(o[r],s[r]);if(t)if(n)for(o=o||mt(e),s=s||mt(a),r=0,i=o.length;i>r;r++)gt(o[r],s[r]);else gt(e,a);return s=mt(a,"script"),s.length>0&&dt(s,!u&&mt(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,p=e.length,f=t.createDocumentFragment(),h=[];for(;p>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(rt.test(i)){o=o||f.appendChild(t.createElement("div")),s=(nt.exec(i)||["",""])[1].toLowerCase(),a=ct[s]||ct._default,o.innerHTML=a[1]+i.replace(tt,"<$1>$2>")+a[2],l=a[0];while(l--)o=o.lastChild;x.merge(h,o.childNodes),o=f.firstChild,o.textContent=""}else h.push(t.createTextNode(i));f.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=mt(f.appendChild(i),"script"),u&&dt(o),n)){l=0;while(i=o[l++])at.test(i.type||"")&&n.push(i)}return f},cleanData:function(e){var t,n,r,i,o,s,a=x.event.special,u=0;for(;(n=e[u])!==undefined;u++){if(F.accepts(n)&&(o=n[q.expando],o&&(t=q.cache[o]))){if(r=Object.keys(t.events||{}),r.length)for(s=0;(i=r[s])!==undefined;s++)a[i]?x.event.remove(n,i):x.removeEvent(n,i,t.handle);q.cache[o]&&delete q.cache[o]}delete L.cache[n[L.expando]]}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}});function pt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function ht(e){var t=ut.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function dt(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function gt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=q.set(t,o),l=o.events)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function mt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function yt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ot.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var vt,xt,bt=/^(none|table(?!-c[ea]).+)/,wt=/^margin/,Tt=RegExp("^("+b+")(.*)$","i"),Ct=RegExp("^("+b+")(?!px)[a-z%]+$","i"),kt=RegExp("^([+-])=("+b+")","i"),Nt={BODY:"block"},Et={position:"absolute",visibility:"hidden",display:"block"},St={letterSpacing:0,fontWeight:400},jt=["Top","Right","Bottom","Left"],Dt=["Webkit","O","Moz","ms"];function At(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Dt.length;while(i--)if(t=Dt[i]+n,t in e)return t;return r}function Lt(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function qt(t){return e.getComputedStyle(t,null)}function Ht(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&Lt(r)&&(o[s]=q.access(r,"olddisplay",Rt(r.nodeName)))):o[s]||(i=Lt(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=qt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return Ht(this,!0)},hide:function(){return Ht(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){Lt(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=vt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=At(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=kt.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=At(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=vt(e,t,r)),"normal"===i&&t in St&&(i=St[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),vt=function(e,t,n){var r,i,o,s=n||qt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Ct.test(a)&&wt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ot(e,t,n){var r=Tt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ft(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+jt[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+jt[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+jt[o]+"Width",!0,i))):(s+=x.css(e,"padding"+jt[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+jt[o]+"Width",!0,i)));return s}function Pt(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=qt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=vt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Ct.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ft(e,t,n||(s?"border":"content"),r,o)+"px"}function Rt(e){var t=o,n=Nt[e];return n||(n=Mt(e,t),"none"!==n&&n||(xt=(xt||x("").css("cssText","display:block !important")).appendTo(t.documentElement),t=(xt[0].contentWindow||xt[0].contentDocument).document,t.write(""),t.close(),n=Mt(e,t),xt.detach()),Nt[e]=n),n}function Mt(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,t){x.cssHooks[t]={get:function(e,n,r){return n?0===e.offsetWidth&&bt.test(x.css(e,"display"))?x.swap(e,Et,function(){return Pt(e,t,r)}):Pt(e,t,r):undefined},set:function(e,n,r){var i=r&&qt(e);return Ot(e,n,r?Ft(e,t,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,t){return t?x.swap(e,{display:"inline-block"},vt,[e,"marginRight"]):undefined}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,t){x.cssHooks[t]={get:function(e,n){return n?(n=vt(e,t),Ct.test(n)?x(e).position()[t]+"px":n):undefined}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+jt[r]+t]=o[r]||o[r-2]||o[0];return i}},wt.test(e)||(x.cssHooks[e+t].set=Ot)});var Wt=/%20/g,$t=/\[\]$/,Bt=/\r?\n/g,It=/^(?:submit|button|image|reset|file)$/i,zt=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&zt.test(this.nodeName)&&!It.test(e)&&(this.checked||!ot.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(Bt,"\r\n")}}):{name:t.name,value:n.replace(Bt,"\r\n")}}).get()}}),x.param=function(e,t){var n,r=[],i=function(e,t){t=x.isFunction(t)?t():null==t?"":t,r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(t===undefined&&(t=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){i(this.name,this.value)});else for(n in e)_t(n,e[n],t,i);return r.join("&").replace(Wt,"+")};function _t(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||$t.test(e)?r(e,i):_t(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)_t(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)
-},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var Xt,Ut,Yt=x.now(),Vt=/\?/,Gt=/#.*$/,Jt=/([?&])_=[^&]*/,Qt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Kt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Zt=/^(?:GET|HEAD)$/,en=/^\/\//,tn=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,nn=x.fn.load,rn={},on={},sn="*/".concat("*");try{Ut=i.href}catch(an){Ut=o.createElement("a"),Ut.href="",Ut=Ut.href}Xt=tn.exec(Ut.toLowerCase())||[];function un(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(w)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function ln(e,t,n,r){var i={},o=e===on;function s(a){var u;return i[a]=!0,x.each(e[a]||[],function(e,a){var l=a(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):undefined:(t.dataTypes.unshift(l),s(l),!1)}),u}return s(t.dataTypes[0])||!i["*"]&&s("*")}function cn(e,t){var n,r,i=x.ajaxSettings.flatOptions||{};for(n in t)t[n]!==undefined&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,t,n){if("string"!=typeof e&&nn)return nn.apply(this,arguments);var r,i,o,s=this,a=e.indexOf(" ");return a>=0&&(r=e.slice(a),e=e.slice(0,a)),x.isFunction(t)?(n=t,t=undefined):t&&"object"==typeof t&&(i="POST"),s.length>0&&x.ajax({url:e,type:i,dataType:"html",data:t}).done(function(e){o=arguments,s.html(r?x("").append(x.parseHTML(e)).find(r):e)}).complete(n&&function(e,t){s.each(n,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ut,type:"GET",isLocal:Kt.test(Xt[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":sn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?cn(cn(e,x.ajaxSettings),t):cn(x.ajaxSettings,e)},ajaxPrefilter:un(rn),ajaxTransport:un(on),ajax:function(e,t){"object"==typeof e&&(t=e,e=undefined),t=t||{};var n,r,i,o,s,a,u,l,c=x.ajaxSetup({},t),p=c.context||c,f=c.context&&(p.nodeType||p.jquery)?x(p):x.event,h=x.Deferred(),d=x.Callbacks("once memory"),g=c.statusCode||{},m={},y={},v=0,b="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(2===v){if(!o){o={};while(t=Qt.exec(i))o[t[1].toLowerCase()]=t[2]}t=o[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===v?i:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return v||(e=y[n]=y[n]||e,m[e]=t),this},overrideMimeType:function(e){return v||(c.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>v)for(t in e)g[t]=[g[t],e[t]];else T.always(e[T.status]);return this},abort:function(e){var t=e||b;return n&&n.abort(t),k(0,t),this}};if(h.promise(T).complete=d.add,T.success=T.done,T.error=T.fail,c.url=((e||c.url||Ut)+"").replace(Gt,"").replace(en,Xt[1]+"//"),c.type=t.method||t.type||c.method||c.type,c.dataTypes=x.trim(c.dataType||"*").toLowerCase().match(w)||[""],null==c.crossDomain&&(a=tn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===Xt[1]&&a[2]===Xt[2]&&(a[3]||("http:"===a[1]?"80":"443"))===(Xt[3]||("http:"===Xt[1]?"80":"443")))),c.data&&c.processData&&"string"!=typeof c.data&&(c.data=x.param(c.data,c.traditional)),ln(rn,c,t,T),2===v)return T;u=c.global,u&&0===x.active++&&x.event.trigger("ajaxStart"),c.type=c.type.toUpperCase(),c.hasContent=!Zt.test(c.type),r=c.url,c.hasContent||(c.data&&(r=c.url+=(Vt.test(r)?"&":"?")+c.data,delete c.data),c.cache===!1&&(c.url=Jt.test(r)?r.replace(Jt,"$1_="+Yt++):r+(Vt.test(r)?"&":"?")+"_="+Yt++)),c.ifModified&&(x.lastModified[r]&&T.setRequestHeader("If-Modified-Since",x.lastModified[r]),x.etag[r]&&T.setRequestHeader("If-None-Match",x.etag[r])),(c.data&&c.hasContent&&c.contentType!==!1||t.contentType)&&T.setRequestHeader("Content-Type",c.contentType),T.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+("*"!==c.dataTypes[0]?", "+sn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)T.setRequestHeader(l,c.headers[l]);if(c.beforeSend&&(c.beforeSend.call(p,T,c)===!1||2===v))return T.abort();b="abort";for(l in{success:1,error:1,complete:1})T[l](c[l]);if(n=ln(on,c,t,T)){T.readyState=1,u&&f.trigger("ajaxSend",[T,c]),c.async&&c.timeout>0&&(s=setTimeout(function(){T.abort("timeout")},c.timeout));try{v=1,n.send(m,k)}catch(C){if(!(2>v))throw C;k(-1,C)}}else k(-1,"No Transport");function k(e,t,o,a){var l,m,y,b,w,C=t;2!==v&&(v=2,s&&clearTimeout(s),n=undefined,i=a||"",T.readyState=e>0?4:0,l=e>=200&&300>e||304===e,o&&(b=pn(c,T,o)),b=fn(c,b,T,l),l?(c.ifModified&&(w=T.getResponseHeader("Last-Modified"),w&&(x.lastModified[r]=w),w=T.getResponseHeader("etag"),w&&(x.etag[r]=w)),204===e||"HEAD"===c.type?C="nocontent":304===e?C="notmodified":(C=b.state,m=b.data,y=b.error,l=!y)):(y=C,(e||!C)&&(C="error",0>e&&(e=0))),T.status=e,T.statusText=(t||C)+"",l?h.resolveWith(p,[m,C,T]):h.rejectWith(p,[T,C,y]),T.statusCode(g),g=undefined,u&&f.trigger(l?"ajaxSuccess":"ajaxError",[T,c,l?m:y]),d.fireWith(p,[T,C]),u&&(f.trigger("ajaxComplete",[T,c]),--x.active||x.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,t){return x.get(e,undefined,t,"script")}}),x.each(["get","post"],function(e,t){x[t]=function(e,n,r,i){return x.isFunction(n)&&(i=i||r,r=n,n=undefined),x.ajax({url:e,type:t,dataType:i,data:n,success:r})}});function pn(e,t,n){var r,i,o,s,a=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),r===undefined&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in a)if(a[i]&&a[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}s||(s=i)}o=o||s}return o?(o!==u[0]&&u.unshift(o),n[o]):undefined}function fn(e,t,n,r){var i,o,s,a,u,l={},c=e.dataTypes.slice();if(c[1])for(s in e.converters)l[s.toLowerCase()]=e.converters[s];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(s=l[u+" "+o]||l["* "+o],!s)for(i in l)if(a=i.split(" "),a[1]===o&&(s=l[u+" "+a[0]]||l["* "+a[0]])){s===!0?s=l[i]:l[i]!==!0&&(o=a[0],c.unshift(a[1]));break}if(s!==!0)if(s&&e["throws"])t=s(t);else try{t=s(t)}catch(p){return{state:"parsererror",error:s?p:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===undefined&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),x.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(r,i){t=x("
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -->
-
-
-
+
+
\ No newline at end of file
diff --git a/php/access/Access.php b/php/access/Access.php
new file mode 100644
index 0000000..c8b3657
--- /dev/null
+++ b/php/access/Access.php
@@ -0,0 +1,30 @@
+database = $database;
+ $this->plugins = $plugins;
+ $this->settings = $settings;
+
+ return true;
+
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/php/access/Admin.php b/php/access/Admin.php
new file mode 100644
index 0000000..2dd1242
--- /dev/null
+++ b/php/access/Admin.php
@@ -0,0 +1,306 @@
+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();
+
+ }
+
+}
\ No newline at end of file
diff --git a/php/access/Guest.php b/php/access/Guest.php
new file mode 100644
index 0000000..280f304
--- /dev/null
+++ b/php/access/Guest.php
@@ -0,0 +1,176 @@
+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!');
+
+ }
+
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/php/access/Installation.php b/php/access/Installation.php
new file mode 100644
index 0000000..dd03925
--- /dev/null
+++ b/php/access/Installation.php
@@ -0,0 +1,39 @@
+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']);
+
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/php/access/admin.php b/php/access/admin.php
deleted file mode 100644
index f6c11dd..0000000
--- a/php/access/admin.php
+++ /dev/null
@@ -1,157 +0,0 @@
-
\ No newline at end of file
diff --git a/php/access/guest.php b/php/access/guest.php
deleted file mode 100644
index 1810c8f..0000000
--- a/php/access/guest.php
+++ /dev/null
@@ -1,126 +0,0 @@
-
\ No newline at end of file
diff --git a/php/access/installation.php b/php/access/installation.php
deleted file mode 100644
index a5d2b22..0000000
--- a/php/access/installation.php
+++ /dev/null
@@ -1,23 +0,0 @@
-
\ No newline at end of file
diff --git a/php/api.php b/php/api.php
index cfdfa08..a8ab85e 100755
--- a/php/api.php
+++ b/php/api.php
@@ -1,10 +1,10 @@
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!');
}
diff --git a/php/autoload.php b/php/autoload.php
new file mode 100644
index 0000000..b2dae0c
--- /dev/null
+++ b/php/autoload.php
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/php/database/albums_table.sql b/php/database/albums_table.sql
new file mode 100644
index 0000000..cce7095
--- /dev/null
+++ b/php/database/albums_table.sql
@@ -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;
\ No newline at end of file
diff --git a/php/database/log_table.sql b/php/database/log_table.sql
new file mode 100644
index 0000000..5538cc2
--- /dev/null
+++ b/php/database/log_table.sql
@@ -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;
diff --git a/php/database/photos_table.sql b/php/database/photos_table.sql
new file mode 100644
index 0000000..ce12386
--- /dev/null
+++ b/php/database/photos_table.sql
@@ -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;
\ No newline at end of file
diff --git a/php/database/settings_content.sql b/php/database/settings_content.sql
new file mode 100644
index 0000000..7a7d20f
--- /dev/null
+++ b/php/database/settings_content.sql
@@ -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','');
\ No newline at end of file
diff --git a/php/database/settings_table.sql b/php/database/settings_table.sql
new file mode 100644
index 0000000..78d4f40
--- /dev/null
+++ b/php/database/settings_table.sql
@@ -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;
\ No newline at end of file
diff --git a/php/database/update_020100.php b/php/database/update_020100.php
new file mode 100644
index 0000000..ccb6d9d
--- /dev/null
+++ b/php/database/update_020100.php
@@ -0,0 +1,41 @@
+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;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/php/database/update_020101.php b/php/database/update_020101.php
new file mode 100644
index 0000000..3522924
--- /dev/null
+++ b/php/database/update_020101.php
@@ -0,0 +1,21 @@
+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;
+}
+
+?>
\ No newline at end of file
diff --git a/php/database/update_020200.php b/php/database/update_020200.php
new file mode 100644
index 0000000..ba44254
--- /dev/null
+++ b/php/database/update_020200.php
@@ -0,0 +1,23 @@
+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;
+}
+
+?>
\ No newline at end of file
diff --git a/php/database/update_020500.php b/php/database/update_020500.php
new file mode 100644
index 0000000..7f4af25
--- /dev/null
+++ b/php/database/update_020500.php
@@ -0,0 +1,133 @@
+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;
+}
+
+?>
\ No newline at end of file
diff --git a/php/define.php b/php/define.php
new file mode 100644
index 0000000..589e359
--- /dev/null
+++ b/php/define.php
@@ -0,0 +1,24 @@
+
\ No newline at end of file
diff --git a/php/modules/Album.php b/php/modules/Album.php
new file mode 100644
index 0000000..a8cfca6
--- /dev/null
+++ b/php/modules/Album.php
@@ -0,0 +1,557 @@
+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;
+
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/php/modules/Database.php b/php/modules/Database.php
new file mode 100755
index 0000000..aeebd19
--- /dev/null
+++ b/php/modules/Database.php
@@ -0,0 +1,212 @@
+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 = "";
+
+ # 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;
+
+ }
+
+}
+
+?>
diff --git a/php/modules/Import.php b/php/modules/Import.php
new file mode 100644
index 0000000..3dfa3c1
--- /dev/null
+++ b/php/modules/Import.php
@@ -0,0 +1,197 @@
+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;
+
+ }
+
+}
+
+?>
diff --git a/php/modules/Log.php b/php/modules/Log.php
new file mode 100644
index 0000000..00f06b8
--- /dev/null
+++ b/php/modules/Log.php
@@ -0,0 +1,56 @@
+query($query);
+
+ if (!$result) return false;
+ return true;
+
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/php/modules/Module.php b/php/modules/Module.php
new file mode 100644
index 0000000..60f43ed
--- /dev/null
+++ b/php/modules/Module.php
@@ -0,0 +1,37 @@
+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.');
+
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/php/modules/Photo.php b/php/modules/Photo.php
new file mode 100755
index 0000000..b5f6da2
--- /dev/null
+++ b/php/modules/Photo.php
@@ -0,0 +1,796 @@
+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;
+
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/php/modules/Plugins.php b/php/modules/Plugins.php
new file mode 100644
index 0000000..2603c04
--- /dev/null
+++ b/php/modules/Plugins.php
@@ -0,0 +1,96 @@
+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;
+
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/php/modules/Session.php b/php/modules/Session.php
new file mode 100755
index 0000000..137c098
--- /dev/null
+++ b/php/modules/Session.php
@@ -0,0 +1,124 @@
+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;
+
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/php/modules/Settings.php b/php/modules/Settings.php
new file mode 100755
index 0000000..46253cb
--- /dev/null
+++ b/php/modules/Settings.php
@@ -0,0 +1,191 @@
+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;
+
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/php/modules/album.php b/php/modules/album.php
deleted file mode 100755
index 7c077d2..0000000
--- a/php/modules/album.php
+++ /dev/null
@@ -1,353 +0,0 @@
-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
".$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;
-
-}
-
-?>
\ No newline at end of file
diff --git a/php/modules/db.php b/php/modules/db.php
deleted file mode 100755
index 12ffb40..0000000
--- a/php/modules/db.php
+++ /dev/null
@@ -1,185 +0,0 @@
-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 = "";
-
- 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;
-
-}
-
-?>
diff --git a/php/modules/misc.php b/php/modules/misc.php
index cd6bea7..2b5afb3 100755
--- a/php/modules/misc.php
+++ b/php/modules/misc.php
@@ -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;
}
-?>
+?>
\ No newline at end of file
diff --git a/php/modules/photo.php b/php/modules/photo.php
deleted file mode 100755
index f000c53..0000000
--- a/php/modules/photo.php
+++ /dev/null
@@ -1,199 +0,0 @@
-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;
-
-}
-
-?>
\ No newline at end of file
diff --git a/php/modules/session.php b/php/modules/session.php
deleted file mode 100755
index fecd555..0000000
--- a/php/modules/session.php
+++ /dev/null
@@ -1,69 +0,0 @@
-
\ No newline at end of file
diff --git a/php/modules/settings.php b/php/modules/settings.php
deleted file mode 100755
index 94da29c..0000000
--- a/php/modules/settings.php
+++ /dev/null
@@ -1,136 +0,0 @@
-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;
-
-}
-
-?>
\ No newline at end of file
diff --git a/php/modules/upload.php b/php/modules/upload.php
deleted file mode 100755
index 4aae792..0000000
--- a/php/modules/upload.php
+++ /dev/null
@@ -1,407 +0,0 @@
-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;
-
-}
-
-?>
\ No newline at end of file
diff --git a/plugins/check.php b/plugins/check.php
deleted file mode 100644
index 51f6eb3..0000000
--- a/plugins/check.php
+++ /dev/null
@@ -1,70 +0,0 @@
-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);
-
-?>
\ No newline at end of file
diff --git a/plugins/check/index.php b/plugins/check/index.php
new file mode 100644
index 0000000..674ad9c
--- /dev/null
+++ b/plugins/check/index.php
@@ -0,0 +1,81 @@
+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);
+
+?>
\ No newline at end of file
diff --git a/plugins/displaylog/index.php b/plugins/displaylog/index.php
new file mode 100644
index 0000000..632c07f
--- /dev/null
+++ b/plugins/displaylog/index.php
@@ -0,0 +1,54 @@
+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]);
+ }
+
+}
+
+?>
diff --git a/readme.md b/readme.md
index 122dc6d..df38b49 100644
--- a/readme.md
+++ b/readme.md
@@ -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 »](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 »](https://gist.github.com/tobru/9171700) |
| lychee-redirect | Redirect from an album-name to a Lychee-album | [More »](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 |
|:-----------|:------------|
diff --git a/etc/assets/thumbs.sketch/fonts b/uploads/medium/index.html
old mode 100644
new mode 100755
similarity index 100%
rename from etc/assets/thumbs.sketch/fonts
rename to uploads/medium/index.html
diff --git a/view.php b/view.php
index a091fa9..c610dd7 100644
--- a/view.php
+++ b/view.php
@@ -3,33 +3,35 @@
-
+
Lychee
-
+
-
+
+
+
+
-
-
+
+
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 @@
-
-
-
+