diff --git a/admin.php b/admin.php index 218afcc4d..f33b5f71d 100644 --- a/admin.php +++ b/admin.php @@ -394,7 +394,7 @@ $template->assign( 'WHATS_NEW_MAJOR_VERSION' => $whats_new_major_version, 'RELEASE_NOTE_URL' => $release_note_url, 'WHATS_NEW_IMGS' => $whats_new_imgs, - 'DISPLAY_BELL' => $display_bell, + 'DISPLAY_BELL' => false, // $display_bell, ) ); diff --git a/admin/include/functions.php b/admin/include/functions.php index 71b3c2706..654ce4515 100644 --- a/admin/include/functions.php +++ b/admin/include/functions.php @@ -3636,6 +3636,30 @@ SELECT return; } } + + // search for duplicate paths + $query = ' +SELECT + path + FROM '.IMAGES_TABLE.' + GROUP BY path + HAVING COUNT(*) > 1 +;'; + $duplicate_paths = query2array($query); + + if (count($duplicate_paths) > 0) + { + global $template; + + $template->assign( + 'header_msgs', + array( + l10n('We have found %d duplicate paths. Details provided by plugin Check Uploads', count($duplicate_paths)), + ) + ); + + return; + } } /** diff --git a/admin/include/functions_upload.inc.php b/admin/include/functions_upload.inc.php index b5eb59b35..46e0fdaf9 100644 --- a/admin/include/functions_upload.inc.php +++ b/admin/include/functions_upload.inc.php @@ -227,7 +227,7 @@ SELECT // compute file path $date_string = preg_replace('/[^\d]/', '', $dbnow); - $random_string = substr($md5sum, 0, 8); + $random_string = substr($md5sum, 0, 4).'%s'; $filename_wo_ext = $date_string.'-'.$random_string; $file_path = $upload_dir.'/'.$filename_wo_ext.'.'; @@ -270,6 +270,16 @@ SELECT } prepare_directory($upload_dir); + + $file_path_pattern = $file_path; + do + { + // we generate a random string for each upload. If the user uploads + // the same photo twice at the same time (same timestamp, same md5sum) + // we still want the path to be unique. + $file_path = sprintf($file_path_pattern, substr(bin2hex(random_bytes(4)), 0, 4)); + } + while (file_exists($file_path)); } if (is_uploaded_file($source_filepath)) @@ -510,8 +520,36 @@ SELECT 'filesize' => $file_infos['filesize'], ); - single_insert(IMAGE_FORMAT_TABLE, $insert); - $format_id = pwg_db_insert_id(IMAGE_FORMAT_TABLE); + + $query = ' +SELECT + format_id + FROM '.IMAGE_FORMAT_TABLE.' + WHERE image_id = '.$format_of.' + AND ext = "'.$format_ext.'" +;'; + + $formats = query2array($query); + if($formats) + { + $set_fields = array( + 'filesize' => $file_infos['filesize'], + ); + $where_fields = array( + 'format_id' => $formats[0]['format_id'], + 'image_id' => $format_of, + 'ext' => $format_ext, + ); + single_update(IMAGE_FORMAT_TABLE, $set_fields, $where_fields); + $format_id = $formats[0]['format_id']; + $add_status = "update"; + } + else + { + single_insert(IMAGE_FORMAT_TABLE, $insert); + $format_id = pwg_db_insert_id(IMAGE_FORMAT_TABLE); + $add_status = "add"; + } pwg_activity('photo', $format_of, 'edit', array('action'=>'add format', 'format_ext'=>$format_ext, 'format_id'=>$format_id)); @@ -520,7 +558,7 @@ SELECT trigger_notify('loc_end_add_format', $format_infos); - return $format_id; + return $add_status; } add_event_handler('upload_file', 'upload_file_pdf'); diff --git a/admin/intro.php b/admin/intro.php index c9629a5fe..356f4328e 100644 --- a/admin/intro.php +++ b/admin/intro.php @@ -100,13 +100,41 @@ fs_quick_check(); $template->set_filenames(array('intro' => 'intro.tpl')); if ($conf['show_newsletter_subscription'] and userprefs_get_param('show_newsletter_subscription', true)) { - $template->assign( - array( - 'EMAIL' => $user['email'], - 'SUBSCRIBE_BASE_URL' => get_newsletter_subscribe_base_url($user['language']), - 'OLD_NEWSLETTERS_URL' => get_old_newsletters_base_url($user['language']), - ) - ); + $query = ' + SELECT registration_date + FROM '.USER_INFOS_TABLE.' + WHERE registration_date IS NOT NULL + ORDER BY user_id ASC + LIMIT 1 + ;'; + list($register_date) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' + SELECT COUNT(*) + FROM '.CATEGORIES_TABLE.' + ;'; + list($nb_cats) = pwg_db_fetch_row(pwg_query($query)); + + $query = ' + SELECT COUNT(*) + FROM '.IMAGES_TABLE.' + ;'; + list($nb_images) = pwg_db_fetch_row(pwg_query($query)); + + include_once(PHPWG_ROOT_PATH.'include/mdetect.php'); + $uagent_obj = new uagent_info(); + // To see the newsletter promote, the account must have 2 weeks ancient, 3 albums created and 30 photos uploaded + + if (!$uagent_obj->DetectIos() and strtotime($register_date) < strtotime('2 weeks ago') and $nb_cats >= 3 and $nb_images >= 30){ + $template->assign( + array( + 'EMAIL' => $user['email'], + 'SUBSCRIBE_BASE_URL' => get_newsletter_subscribe_base_url($user['language']), + 'OLD_NEWSLETTERS_URL' => get_old_newsletters_base_url($user['language']), + ) + ); + } + } diff --git a/admin/photos_add_direct.php b/admin/photos_add_direct.php index e66fbbbc7..21fc3e372 100644 --- a/admin/photos_add_direct.php +++ b/admin/photos_add_direct.php @@ -26,7 +26,7 @@ DELETE FROM '.CADDIE_TABLE.' pwg_query($query); $inserts = array(); - foreach (explode(',', $_GET['batch']) as $image_id) + foreach (array_unique(explode(',', $_GET['batch'])) as $image_id) { $inserts[] = array( 'user_id' => $user['id'], @@ -85,6 +85,7 @@ $display_formats = $conf['enable_formats'] && isset($_GET['formats']); $have_formats_original = false; $formats_original_info = array(); +$formats_ext_info = null; // If URL parameter isn't empty if ($display_formats && $_GET['formats']) @@ -109,13 +110,16 @@ SELECT * if (!empty($formats)) { $format_strings = array(); + $formats_exts = array(); foreach ($formats as $format) { $format_strings[] = sprintf('%s (%.2fMB)', $format['ext'], $format['filesize']/1024); + $formats_exts[] = strtolower($format['ext']); } $formats_original_info['formats'] = l10n('Formats: %s', implode(', ', $format_strings)); + $formats_ext_info = json_encode($formats_exts); } $extTab = explode('.',$formats_original_info['file']); @@ -150,7 +154,8 @@ $template->assign(array( 'DISPLAY_FORMATS' => $display_formats, 'HAVE_FORMATS_ORIGINAL' => $have_formats_original, 'FORMATS_ORIGINAL_INFO' => $formats_original_info, - 'SWITCH_MODE_URL' => get_root_url().'admin.php?page=photos_add'.($display_formats ? '':'&formats'), + 'FORMATS_EXT_INFO' => $formats_ext_info, + 'SWITCH_FORMAT_MODE_URL' => get_root_url().'admin.php?page=photos_add'.($display_formats ? '':'&formats'), 'format_ext' => implode(',', $conf['format_ext']), 'str_format_ext' => implode(', ', $conf['format_ext']), )); diff --git a/admin/themes/clear/theme.css b/admin/themes/clear/theme.css index 8346c91fe..039450e1a 100644 --- a/admin/themes/clear/theme.css +++ b/admin/themes/clear/theme.css @@ -682,6 +682,9 @@ a#showPermissions:hover {border-color: #A5A5A5;} .plupload_filelist_footer {background-color: #F5F5F5!important;} li.plupload_delete a {background: url("images/cancel.svg")!important; background-size: cover!important;} li.plupload_delete a:hover {background: url("images/cancelhover.svg")!important; background-size: cover!important;} +li.plupload_delete a span {background: #FFF;} +li.plupload_delete a:hover span {background: #FFF;} +li.plupload_delete a.remove-format:hover{color: #474747;} .addAlbumEmpty {color: #3C3C3C;} #permitAction p {background: #FFF;} @@ -731,6 +734,13 @@ li.plupload_delete a:hover {background: url("images/cancelhover.svg")!important; #batchManagerGlobal .ui-slider-range.ui-widget-header.ui-corner-all {border: 1px solid #ffaf58;} #batchManagerGlobal .font-checkbox.selected {color: #777;} +/* Picture add */ + +.upload-options, .upload-options-content { + background-color: #f5f5f5; + color: #777777; +} + /* Category List */ .categoryContainer { margin: 0; diff --git a/admin/themes/default/css/components/album_selector.css b/admin/themes/default/css/components/album_selector.css index f99a1c019..ad23fd37d 100644 --- a/admin/themes/default/css/components/album_selector.css +++ b/admin/themes/default/css/components/album_selector.css @@ -164,6 +164,11 @@ text-align: left; } +.search-result-path.not-rtl { + direction: ltr !important; + text-overflow: unset !important; +} + .search-result-path-name { unicode-bidi: plaintext; } diff --git a/admin/themes/default/js/album_selector.js b/admin/themes/default/js/album_selector.js index d46363438..6fad3ec7e 100644 --- a/admin/themes/default/js/album_selector.js +++ b/admin/themes/default/js/album_selector.js @@ -551,7 +551,7 @@ class AlbumSelector { AlbumSelector.selectors.searchResult.append( `
- ${cat_name} + ${this.#getEllipsisName(cat_name)}
` ); @@ -572,6 +572,10 @@ class AlbumSelector { !this.#isAlbumCreationChecked && this.#loadFillResultEvent(tempSelectedCat); } + #getEllipsisName(str, lenght = 50) { + if (str.length <= lenght) return str; + return '...' + str.slice(-lenght).trim(); + } /*----------- Ajax method -----------*/ diff --git a/admin/themes/default/js/photos_add_direct.js b/admin/themes/default/js/photos_add_direct.js index 975052ff8..f46a033e2 100644 --- a/admin/themes/default/js/photos_add_direct.js +++ b/admin/themes/default/js/photos_add_direct.js @@ -16,6 +16,8 @@ const selectedAlbumEdit = $('#selectedAlbumEdit'); const btnAddFiles = $('#addFiles'); const chooseAlbumFirst = $('#chooseAlbumFirst'); const uploaderPhotos = $('#uploader'); +const formatsUpdated = []; +const formats = []; /*-------------- On DOM load @@ -85,6 +87,12 @@ $(function () { return false; }); + $("#uploadOptionsContent").hide(); + $("#uploadOptions").on("click", function(){ + $("#uploadOptionsContent").slideToggle(); + $(".moxie-shim-html5").css("display", "none"); + }) + $("#uploader").pluploadQueue({ // General settings browse_button: 'addFiles', @@ -153,11 +161,37 @@ $(function () { FilesAdded: async function (up, files) { // Création de la liste avec plupload_id : image_name fileNames = {}; + exts = {}; files.forEach((file) => { fileNames[file.id] = file.name; + exts[file.id] = file.name.substr(file.name.lastIndexOf('.') + 1); }); if (formatMode) { + formats.forEach((forms) => { + $("#"+forms[0]+" > .plupload_file_name").append(` + + + + `); + if(formatsUpdated.includes(forms[0])){ + $("#"+forms[0]+" > .plupload_file_name").after(` + + + ${format_update_warning} + + + + + + ${format_remove} + `); + $("#remove_"+forms[0]).on("click", function(){ + up.removeFile(forms[0]); + }); + } + }); + // If no original image is specified if (!haveFormatsOriginal) { const images_search = await new Promise((res, rej) => { @@ -166,8 +200,6 @@ $(function () { url: "ws.php?format=json&method=pwg.images.formats.searchImage", type: "POST", data: { - // category_id: $("select[name=category] option:selected").val(), // id category to modify - category_id: ab.get_selected_albums()[0], filename_list: JSON.stringify(fileNames), }, success: function (result) { @@ -182,8 +214,33 @@ $(function () { files.forEach((f) => { const search = images_search[f.id]; - if (search.status == "found") + if (search.status == "found"){ f.format_of = search.image_id; + formats.push([f.id,f.format_of]); + $("#"+f.id+" > .plupload_file_name").append(` + + + + `); + if (search.format_exist) + { + $("#"+f.id+" > .plupload_file_name").after(` + + + ${format_update_warning} + + + + + + ${format_remove} + `); + formatsUpdated.push(f.id); + $("#remove_"+f.id).on("click", function(){ + up.removeFile(f.id); + }); + } + } else { if (search.status == "multiple") multiple.push(f.name); @@ -218,14 +275,72 @@ $(function () { ...jConfirm_warning_options }) } - } else { //If an original image is specified + } else { + if (imageFormatsExtensions) + { + $forms_exts = JSON.parse(imageFormatsExtensions); + } + else + { + $forms_exts = []; + } files.forEach((f) => { f.format_of = originalImageId; + formats.push([f.id,f.format_of]); + $("#"+f.id+" > .plupload_file_name").append(` + + + + `); + if ($forms_exts.indexOf(exts[f.id]) != -1) + { + $("#"+f.id+" > .plupload_file_name").after(` + + + ${format_update_warning} + + + + + + ${format_remove} + `); + formatsUpdated.push(f.id); + $("#remove_"+f.id).on("click", function(){ + up.removeFile(f.id); + }); + } }) } } }, + FilesRemoved: function(up, file){ + formats.forEach((forms) => { + $("#"+forms[0]+" > .plupload_file_name").append(` + + + + `); + if(formatsUpdated.includes(forms[0])){ + $("#"+forms[0]+" > .plupload_file_name").after(` + + + ${format_update_warning} + + + + + + ${format_remove} + `); + $("#remove_"+forms[0]).on("click", function(){ + up.removeFile(forms[0]); + }); + } + }); + }, + UploadProgress: function (up, file) { $('#uploadingActions .progressbar').width(up.total.percent + '%'); Piecon.setProgress(up.total.percent); @@ -265,6 +380,8 @@ $(function () { options.name = file.name; } + options.update_mode = $('#toggleUpdateMode').is(':checked'); + up.setOption('multipart_params', options); }, @@ -289,6 +406,12 @@ $(function () { // do not remove file, or it will reset the progress bar :-/ // up.removeFile(file); uploadedPhotos.push(parseInt(data.result.image_id)); + if(data.result.add_status=="add"){ + addedPhotos.push(parseInt(data.result.image_id)); + } + else{ + updatedPhotos.push(parseInt(data.result.image_id)); + } if (!formatMode) uploadCategory = data.result.category; }, @@ -322,12 +445,23 @@ $(function () { $("#uploadForm, #permissions, .showFieldset").hide(); - const infoText = formatMode ? - sprintf(formatsUploaded_label, uploadedPhotos.length, [...new Set(files.map(f => f.format_of))].length) - : sprintf(photosUploaded_label, uploadedPhotos.length) + const infoTextAdd = formatMode ? + sprintf(formatsAdded_label, addedPhotos.length, [...new Set(addedPhotos)].length) + : sprintf(photosAdded_label, addedPhotos.length); - $(".infos").append(''); + const infoTextUpdate = formatMode ? + sprintf(formatsUpdated_label, updatedPhotos.length, [...new Set(updatedPhotos)].length) + : sprintf(photosUpdated_label, updatedPhotos.length); + if (addedPhotos.length && updatedPhotos.length) + { + $(".infos").append( ''); + } + else + { + const infoText = addedPhotos.length ? infoTextAdd : infoTextUpdate; + $(".infos").append(''); + } if (!formatMode) { html = sprintf( @@ -345,7 +479,7 @@ $(function () { // pwg.caddie.add(uploadedPhotos) instead of relying on huge GET parameter // (and remove useless code from admin/photos_add_direct.php) - $(".batchLink").attr("href", "admin.php?page=photos_add§ion=direct&batch=" + uploadedPhotos.join(",")); + $(".batchLink").attr("href", "admin.php?page=photos_add§ion=direct&batch=" + [...new Set(uploadedPhotos)].join(",")); $(".batchLink").html(sprintf(batch_Label, uploadedPhotos.length)); $(".afterUploadActions").show(); @@ -357,7 +491,6 @@ $(function () { } } }); - }); /*-------------- diff --git a/admin/themes/default/template/maintenance_actions.tpl b/admin/themes/default/template/maintenance_actions.tpl index c9ffa4a22..63c070296 100644 --- a/admin/themes/default/template/maintenance_actions.tpl +++ b/admin/themes/default/template/maintenance_actions.tpl @@ -198,7 +198,7 @@ $(".delete-size-check").click( function () {
{foreach from=$purge_derivatives key=name item=url name=loop} -
+
{$name}
diff --git a/admin/themes/default/template/photos_add_direct.tpl b/admin/themes/default/template/photos_add_direct.tpl index 5353923c8..5c0ede7d4 100644 --- a/admin/themes/default/template/photos_add_direct.tpl +++ b/admin/themes/default/template/photos_add_direct.tpl @@ -35,25 +35,32 @@ const formatMode = {if $DISPLAY_FORMATS}true{else}false{/if}; const haveFormatsOriginal = {if $HAVE_FORMATS_ORIGINAL}true{else}false{/if}; const originalImageId = haveFormatsOriginal? '{if isset($FORMATS_ORIGINAL_INFO['id'])} {$FORMATS_ORIGINAL_INFO['id']} {else} -1 {/if}' : -1; +const imageFormatsExtensions = '{$FORMATS_EXT_INFO}'; const nb_albums = {$NB_ALBUMS|escape:javascript}; const chunk_size = '{$chunk_size}kb'; const max_file_size = '{$max_file_size}mb'; +const format_update_warning = "{'This format already exists, it will be overwritten !'|translate}"; +const format_remove = "{'Remove'|translate}"; var pwg_token = '{$pwg_token}'; -var photosUploaded_label = "{'%d photos uploaded'|translate|escape:javascript}"; -var formatsUploaded_label = "{'%d formats uploaded for %d photos'|translate|escape:javascript}"; -var batch_Label = "{'Manage this set of %d photos'|translate|escape:javascript}"; -var albumSummary_label = "{'Album "%s" now contains %d photos'|translate|escape:javascript}"; -var str_format_warning = "{'Error when trying to detect formats'|translate|escape:javascript}"; -var str_ok = "{'Ok'|translate|escape:javascript}"; -var str_format_warning_multiple = "{'There is multiple image in the database with the following names : %s.'|translate|escape:javascript}"; -var str_format_warning_notFound = "{'No picture found with the following name : %s.'|translate|escape:javascript}"; -var str_and_X_others = "{'and %d more'|translate|escape:javascript}"; +const photosAdded_label = "{'%d photos uploaded'|translate|escape:javascript}"; +const photosUpdated_label = "{'%d photos updated'|translate|escape:javascript}"; +const formatsAdded_label = "{'%d formats added for %d photos'|translate|escape:javascript}"; +const formatsUpdated_label = "{'%d formats updated for %d photos'|translate|escape:javascript}"; +const batch_Label = "{'Manage this set of %d photos'|translate|escape:javascript}"; +const albumSummary_label = "{'Album "%s" now contains %d photos'|translate|escape:javascript}"; +const str_format_warning = "{'Error when trying to detect formats'|translate|escape:javascript}"; +const str_ok = "{'Ok'|translate|escape:javascript}"; +const str_format_warning_multiple = "{'There is multiple image in the database with the following names : %s.'|translate|escape:javascript}"; +const str_format_warning_notFound = "{'No picture found with the following name : %s.'|translate|escape:javascript}"; +const str_and_X_others = "{'and %d more'|translate|escape:javascript}"; const str_upload_in_progress = "{'Upload in progress'|translate|escape:javascript}"; const str_drop_album_ab = '{'Drop into album'|@translate|escape:javascript}'; -var file_ext = "{$file_exts}"; -var format_ext = "{$format_ext}"; -var uploadedPhotos = []; -var uploadCategory = null; +const file_ext = "{$file_exts}"; +const format_ext = "{$format_ext}"; +const uploadedPhotos = []; +let uploadCategory = null; +const addedPhotos = []; +const updatedPhotos = []; let related_categories_ids = {$selected_category|json_encode}; {/footer_script} @@ -103,7 +110,7 @@ let related_categories_ids = {$selected_category|json_encode}; {if $ENABLE_FORMATS and $can_upload}
-
+
+ + {/if} @@ -156,14 +171,31 @@ p.release .errors {margin:0} {if !empty($missing.plugins) or !empty($missing.themes)}

{/if} -

-{if isset($MAJOR_RELEASE_PHP_REQUIRED)} - {'Requires PHP %s'|translate:$MAJOR_RELEASE_PHP_REQUIRED} -{/if} -

- - -

+ + + + + {/if} diff --git a/admin/themes/default/theme.css b/admin/themes/default/theme.css index df1c04ad5..f9e605b26 100644 --- a/admin/themes/default/theme.css +++ b/admin/themes/default/theme.css @@ -1893,6 +1893,36 @@ img.ui-datepicker-trigger { padding-right: 5px; } +.upload-options{ + cursor: pointer; + align-items: center; + margin-left: 25px; + padding: 2.5px 10px 7.5px 0; + display: flex; +} + +.upload-options-icon{ + margin-right: 0 !important; + padding-top: 0 !important; +} + +.upload-options-content{ + display: flex; + position: relative; + font-weight: normal; + padding : 23px 0px 22px 24px; + width: calc(101vw - 100%); + margin-right: 1em; +} + +#toggleUpdateMode + .slider.round{ + margin-right: 0; +} + +.rotate-element{ + transform: rotate(90deg); +} + #uploadFormSettings input[type="text"] { text-align:right; } @@ -1901,6 +1931,45 @@ img.ui-datepicker-trigger { width:50%; } +.plupload_file_name span:not(.icon-eye){ + max-width: 180px; + white-space: nowrap; + text-overflow: ellipsis; + display: inline-block; + overflow: hidden; +} + +.plupload_file_name a{ + margin-left: 5px; + position: relative; + bottom: 3px; +} + +.plupload_file_name + a{ + margin-left: 15%; +} + +.update-warning{ + margin-right: 5px; + color:#ED5F59; + text-decoration: underline; +} + +.update-warning:hover{ + color:#BE4949; +} + +li.plupload_delete a.remove-format{ + margin-left: 5px; + background: none !important; + color: #A4A4A4; +} + +li.plupload_delete a.remove-format:hover{ + background: none !important; + text-decoration: none; +} + html, body {height:100; min-height: 100%; margin:0; padding:0;} #the_page {min-height:100%; position:absolute; width:100%; padding:0;margin:0;} @@ -5407,10 +5476,20 @@ input[type="text"].dError {border-color:#ff7070; background-color:#FFe5e5;} } } +input:checked + .slider.small::before { + -webkit-transform: translateX(12px); + -ms-transform: translateX(12px); + transform: translateX(12px); +} + input:checked + .slider:before, input:checked + .slider::after { margin-left: 33px; } +input:checked + .slider.small:before, input:checked + .slider.small::after { + margin-left: 0px; +} + .slider.loading::before { opacity: 0; } diff --git a/admin/themes/roma/theme.css b/admin/themes/roma/theme.css index 8f2a9b4f2..b91a97eb2 100644 --- a/admin/themes/roma/theme.css +++ b/admin/themes/roma/theme.css @@ -421,6 +421,16 @@ div.token-input-dropdown ul li.token-input-selected-dropdown-item {background-co background: #3c3939; } +/* Picture add */ + +.upload-options, .upload-options-content { + background-color: #343434; +} + +.upload-options-content .switch .slider { + background-color: #444444 +} + /* Picture edit */ #picture-content input[type=text], #picture-content textarea, #picture-content .selectize-input, #picture-content select { background-color: #444444; @@ -710,7 +720,7 @@ input:checked + .slider::after{ } input:checked + .slider { - background-color: #ffa646; + background-color: #ffa646 !important; } input:focus + .slider { @@ -998,6 +1008,9 @@ a#showPermissions:hover {border-color: #A5A5A5;} .plupload_filelist_footer {background-color: #3c3939!important; border: 0!important; color: #A5A5A5!important} li.plupload_delete a {background: url("images/cancel.svg")!important; background-size: cover!important;} li.plupload_delete a:hover {background: url("images/cancelhover.svg")!important; background-size: cover!important;} +li.plupload_delete a span {background: #444444;} +li.plupload_delete a:hover span {background: #444444;} +li.plupload_delete a.remove-format:hover{color: #c0c0c0;} .addAlbumEmpty {color: #c1c1c1;} .buttonSeparator {color: #c1c1c1;} diff --git a/admin/updates_ext.php b/admin/updates_ext.php index abead9a2d..ea3091673 100644 --- a/admin/updates_ext.php +++ b/admin/updates_ext.php @@ -56,6 +56,12 @@ foreach ($autoupdate->types as $type) continue; } + // In dev mode, do not show update actions + if ('auto' === $fs_ext['version']) + { + continue; + } + $ext_info = $server_ext[$fs_ext['extension']]; if (!safe_version_compare($fs_ext['version'], $ext_info['revision_name'], '>=')) diff --git a/admin/user_activity.php b/admin/user_activity.php index 1c38ab0bc..f62d34014 100644 --- a/admin/user_activity.php +++ b/admin/user_activity.php @@ -43,6 +43,7 @@ SELECT '.$conf['user_fields']['username'].' AS username FROM '.ACTIVITY_TABLE.' JOIN '.USERS_TABLE.' AS u ON performed_by = u.'.$conf['user_fields']['id'].' + WHERE object = \'user\' ORDER BY activity_id DESC ;'; @@ -74,7 +75,7 @@ SELECT $f = fopen('php://output', 'w'); foreach ($output_lines as $line) { - fputcsv($f, $line, ";"); + fputcsv($f, $line, ";", '"', '\\'); } fclose($f); diff --git a/include/functions_user.inc.php b/include/functions_user.inc.php index 350c44638..ac19032c7 100644 --- a/include/functions_user.inc.php +++ b/include/functions_user.inc.php @@ -1127,6 +1127,12 @@ function auto_login() $key = calculate_auto_login_key( $cookie[0], $cookie[1], $username ); if ($key!==false and $key===$cookie[2]) { + // Since Piwigo 16, 'connected_with' in the session defines the authentication context (UI, API, etc). + // Auto-login via remember-me may miss this, so we set it to 'pwg_ui' for UI logins (not API). + if (script_basename() != 'ws') + { + $_SESSION['connected_with'] = 'pwg_ui'; + } log_user($cookie[0], true); trigger_notify('login_success', stripslashes($username)); return true; @@ -2633,4 +2639,20 @@ SELECT return $api_keys; } + +/** + * Is connected with pwg_ui (identification.php) + * + * @since 16 + * @return bool + */ +function connected_with_pwg_ui() +{ + // You can manage your api key only if you are connected via identification.php + if (isset($_SESSION['connected_with']) and 'pwg_ui' === $_SESSION['connected_with']) + { + return true; + } + return false; +} ?> diff --git a/include/ws_functions/pwg.images.php b/include/ws_functions/pwg.images.php index a36584f5a..53553679b 100644 --- a/include/ws_functions/pwg.images.php +++ b/include/ws_functions/pwg.images.php @@ -1780,6 +1780,7 @@ function ws_images_upload($params, $service) @fclose($out); @fclose($in); + $add_status = "add"; // Check if file has been uploaded if (!$chunks || $chunk == $chunks - 1) { @@ -1803,22 +1804,44 @@ SELECT * $image = $images[0]; - add_format($filePath, $format_ext, $image['id']); + $add_status = add_format($filePath, $format_ext, $image['id']); return array( 'image_id' => $image['id'], 'src' => DerivativeImage::thumb_url($image), 'square_src' => DerivativeImage::url(ImageStdParams::get_by_type(IMG_SQUARE), $image), 'name' => $image['name'], - ); + 'add_status' => $add_status, + ); + } + + $name = pwg_db_real_escape_string(stripslashes($params['name'])); + $id_image = null; //null by default + + if ($params['update_mode']) + { + $query = ' +SELECT + id + FROM '.IMAGES_TABLE.' AS i + INNER JOIN '.IMAGE_CATEGORY_TABLE.' as ic ON ic.image_id = i.id + WHERE i.file = \''.$name.'\' + AND ic.category_id = '.$params['category'][0].' +;'; + $images = query2array($query); + if ($images != null) + { + $id_image = $images[0]['id']; //take the id of the already existing image to replace it + $add_status = "update"; + } } $image_id = add_uploaded_file( $filePath, - stripslashes($params['name']), // function add_uploaded_file will secure before insert + $name, // function add_uploaded_file will secure before insert $params['category'], $params['level'], - null // image_id = not provided, this is a new photo + $id_image ); $query = ' @@ -1845,6 +1868,7 @@ SELECT COUNT(*) FROM '.LOUNGE_TABLE.' WHERE category_id = '.$params['category'][0].' + AND image_id NOT IN (Select image_id from '.IMAGE_CATEGORY_TABLE.') ;'; list($nb_photos_lounge) = pwg_db_fetch_row(pwg_query($query)); @@ -1859,7 +1883,8 @@ SELECT 'id' => $params['category'][0], 'nb_photos' => $category_infos['nb_photos'] + $nb_photos_lounge, 'label' => $category_name, - ) + ), + 'add_status' => $add_status ); } } @@ -2220,7 +2245,6 @@ SELECT id, file * * @since 13 * @param mixed[] $params - * @option string category_id (optional) * @option string filename_list */ function ws_images_formats_searchImage($params, $service) @@ -2251,6 +2275,19 @@ SELECT return strlen($b) - strlen($a); }); + $query = ' +SELECT + image_id, + ext + FROM '.IMAGE_FORMAT_TABLE.' +;'; + $result = pwg_query($query); + while ($row = pwg_db_fetch_assoc($result)) + { + $format_image_id = $row['image_id']; + @$format_db[ $format_image_id ][] = $row['ext']; + } + $result = array(); foreach ($candidates as $format_external_id => $format_filename) @@ -2275,8 +2312,17 @@ SELECT $result[$format_external_id] = array('status' => 'multiple'); continue; } - - $result[$format_external_id] = array('status' => 'found', 'image_id' => $unique_filenames_db[$candidate_filename_wo_ext][0]); + $img_id = $unique_filenames_db[$candidate_filename_wo_ext][0]; + $mult_form = false; + if (isset($format_db[$img_id])) + { + $format_ext = pathinfo($format_filename, PATHINFO_EXTENSION); + if (array_search($format_ext, $format_db[$img_id])!==false) + { + $mult_form = true; + } + } + $result[$format_external_id] = array('status' => 'found', 'image_id' => $img_id, 'format_exist' => $mult_form); continue; } diff --git a/include/ws_functions/pwg.users.php b/include/ws_functions/pwg.users.php index baf44f14d..c18add751 100644 --- a/include/ws_functions/pwg.users.php +++ b/include/ws_functions/pwg.users.php @@ -962,7 +962,7 @@ function ws_create_api_key($params, &$service) { global $user, $logger; - if (is_a_guest() OR !can_manage_api_key()) return new PwgError(401, 'Acces Denied'); + if (is_a_guest() OR !connected_with_pwg_ui()) return new PwgError(401, 'Acces Denied'); if (get_pwg_token() != $params['pwg_token']) { @@ -999,7 +999,7 @@ function ws_revoke_api_key($params, &$service) { global $user, $logger; - if (is_a_guest() OR !can_manage_api_key()) return new PwgError(401, 'Acces Denied'); + if (is_a_guest() OR !connected_with_pwg_ui()) return new PwgError(401, 'Acces Denied'); if (get_pwg_token() != $params['pwg_token']) { @@ -1038,7 +1038,7 @@ function ws_edit_api_key($params, &$service) return new PwgError(401, 'Acces Denied'); } - if (!can_manage_api_key()) + if (!connected_with_pwg_ui()) { return new PwgError(401, 'Acces Denied'); } @@ -1081,7 +1081,7 @@ function ws_get_api_key($params, &$service) return new PwgError(401, 'Acces Denied'); } - if (!can_manage_api_key()) + if (!connected_with_pwg_ui()) { return new PwgError(401, 'Acces Denied'); } @@ -1095,14 +1095,4 @@ function ws_get_api_key($params, &$service) return $api_keys ?? l10n('No API key found'); } - -function can_manage_api_key() -{ - // You can manage your api key only if you are connected via identification.php - if (isset($_SESSION['connected_with']) and 'pwg_ui' === $_SESSION['connected_with']) - { - return true; - } - return false; -} ?> diff --git a/install.php b/install.php index 738cd03c0..e4b029040 100644 --- a/install.php +++ b/install.php @@ -483,6 +483,7 @@ else // cache requires $logger which is not instanciated $user = build_user(1, false); log_user($user['id'], false); + $_SESSION['connected_with'] = 'pwg_ui'; $user['preferences']['show_whats_new_'.get_branch_from_version(PHPWG_VERSION)] = false; diff --git a/install/db/177-database.php b/install/db/177-database.php index 49004fb84..259c901f2 100644 --- a/install/db/177-database.php +++ b/install/db/177-database.php @@ -23,6 +23,37 @@ $derivatives = unserialize($conf['derivatives']); $default_sizes = ImageStdParams::get_default_sizes(); //Get 3XL and 4XL from default values +$default_derivative_3XL = $default_sizes['3xlarge']; +$default_derivative_4XL = $default_sizes['4xlarge']; + +//We need to make sure that a user hasn't redefined the XXL size bigger than the default 3XL +//Get xxl size +$xxl = $derivatives['d']['xxlarge']; +$xxl_height = $xxl->sizing->ideal_size[0]; +$xxl_width = $xxl->sizing->ideal_size[1]; + +//get 3xl size +$triple_xl_height = $default_sizes['3xlarge']->sizing->ideal_size[0]; +$triple_xl_width = $default_sizes['3xlarge']->sizing->ideal_size[1]; + +//Get 4xl size +$quad_xl_height = $default_sizes['3xlarge']->sizing->ideal_size[0]; +$quad_xl_width = $default_sizes['3xlarge']->sizing->ideal_size[1]; + +//Set 3XL and 4xl size to be bigger than XXL if needed +if ($triple_xl_height < $xxl_height or $triple_xl_width < $xxl_width) +{ + $new_3xl_height = ceil($xxl_height*1.5); + $new_3xl_width = ceil($xxl_width*1.5); + + $default_sizes['3xlarge']->sizing->ideal_size[0] = $new_3xl_height; + $default_sizes['3xlarge']->sizing->ideal_size[1] = $new_3xl_width; + + $default_sizes['4xlarge']->sizing->ideal_size[0] = ceil($new_3xl_width*1.5); + $default_sizes['4xlarge']->sizing->ideal_size[1] = ceil($new_3xl_width*1.5); +} + +//Add new 3xl and 4xl to derivatives sizes config $derivatives['d'][IMG_3XLARGE] = $default_sizes['3xlarge']; $derivatives['d'][IMG_4XLARGE] = $default_sizes['4xlarge']; diff --git a/install/piwigo_structure-mysql.sql b/install/piwigo_structure-mysql.sql index ca47a8420..85f5b7364 100644 --- a/install/piwigo_structure-mysql.sql +++ b/install/piwigo_structure-mysql.sql @@ -410,10 +410,15 @@ DROP TABLE IF EXISTS `piwigo_user_auth_keys`; CREATE TABLE `piwigo_user_auth_keys` ( `auth_key_id` int(11) unsigned NOT NULL AUTO_INCREMENT, `auth_key` varchar(255) NOT NULL, + `apikey_secret` VARCHAR(255) DEFAULT NULL, `user_id` mediumint(8) unsigned NOT NULL, `created_on` datetime NOT NULL, `duration` int(11) unsigned DEFAULT NULL, `expired_on` datetime NOT NULL, + `apikey_name` VARCHAR(100) DEFAULT NULL, + `key_type` VARCHAR(40) DEFAULT NULL, + `revoked_on` datetime DEFAULT NULL, + `last_used_on` datetime DEFAULT NULL, PRIMARY KEY (`auth_key_id`) ) ENGINE=MyISAM; diff --git a/language/en_UK/admin.lang.php b/language/en_UK/admin.lang.php index b229ebc8b..5ab96daf5 100644 --- a/language/en_UK/admin.lang.php +++ b/language/en_UK/admin.lang.php @@ -1410,5 +1410,6 @@ $lang['Welcome to %s'] = 'Welcome to %s'; $lang['Save all photos'] = 'Save all photos'; $lang['Use standard Piwigo template for common pages.'] = 'Use standard Piwigo template for common pages.'; $lang['When enabled, a common template is used for the login, registration and forgotten password pages, regardless of the theme. Some themes might use these templates even if you uncheck this option'] = 'When enabled, a common template is used for the login, registration and forgotten password pages, regardless of the theme. Some themes might use these templates even if you uncheck this option'; +$lang['We have found %d duplicate paths. Details provided by plugin Check Uploads'] = 'We have found %d duplicate paths. Details provided by plugin Check Uploads'; // Leave this line empty \ No newline at end of file diff --git a/language/en_UK/common.lang.php b/language/en_UK/common.lang.php index a62397ed6..441599f9f 100644 --- a/language/en_UK/common.lang.php +++ b/language/en_UK/common.lang.php @@ -517,4 +517,4 @@ $lang['Edit API Key'] = 'Edit API Key'; $lang['Do you really want to revoke the "%s" API key?'] = 'Do you really want to revoke the "%s" API key?'; $lang['To manage your API keys, please log in with your username/password.'] = 'To manage your API keys, please log in with your username/password.'; $lang['3xlarge'] = '3XL - extra huge'; -$lang['4xlarge'] = '4XL - gigantique'; \ No newline at end of file +$lang['4xlarge'] = '4XL - gigantic'; diff --git a/language/fr_FR/admin.lang.php b/language/fr_FR/admin.lang.php index 75144e438..2b0d5b94e 100644 --- a/language/fr_FR/admin.lang.php +++ b/language/fr_FR/admin.lang.php @@ -1412,4 +1412,5 @@ $lang['Welcome to %s'] = 'Bienvenue sur %s'; $lang['Save all photos'] = 'Enregistrer toutes les photos'; $lang['Use standard Piwigo template for common pages.'] = 'Utiliser le modèle standard de Piwigo pour les pages courantes.'; $lang['When enabled, a common template is used for the login, registration and forgotten password pages, regardless of the theme. Some themes might use these templates even if you uncheck this option'] = 'Lorsque cette option est activée, un modèle commun est utilisé pour les pages de connexion, d\'inscription et de mot de passe oublié, quel que soit le thème. Certains thèmes peuvent utiliser ces modèles même si cette option est décochée.'; +$lang['We have found %d duplicate paths. Details provided by plugin Check Uploads'] = 'Nous avons trouvé %d chemins anormalement dupliqués. À contrôler avec le plugin Check Uploads.'; // Leave this line empty diff --git a/themes/standard_pages/js/profile.js b/themes/standard_pages/js/profile.js index c8a59e2fb..4cdceb597 100644 --- a/themes/standard_pages/js/profile.js +++ b/themes/standard_pages/js/profile.js @@ -21,7 +21,9 @@ $(function() { } }); - $('#account-section .display-btn').trigger('click'); + setTimeout(() => { + $('#account-section .display-btn').trigger('click'); + }, 100); $('#save_account').on('click', function() { const mail = $('#email').val(); @@ -32,48 +34,71 @@ $(function() { setInfos({ email: mail }); }); - $('#save_preferences').on('click', function() { - const values = { - nb_image_page: $('#nb_image_page').val(), - theme: $('select[name="theme"]').val(), - language: $('select[name="language"]').val(), - recent_period: $('#recent_period').val(), - expand: $('#opt_album').is(':checked'), - show_nb_comments: $('#opt_comment').is(':checked'), - show_nb_hits: $('#opt_hits').is(':checked') - } + if (canUpdatePreferences) { + $('#save_preferences').on('click', function () { + const values = { + nb_image_page: $('#nb_image_page').val(), + theme: $('select[name="theme"]').val(), + language: $('select[name="language"]').val(), + recent_period: $('#recent_period').val(), + expand: $('#opt_album').is(':checked'), + show_nb_comments: $('#opt_comment').is(':checked'), + show_nb_hits: $('#opt_hits').is(':checked') + } - if (values.nb_image_page == '') { - $('#error_nb_image').show(); - return; - } + if (values.nb_image_page == '') { + $('#error_nb_image').show(); + return; + } - if (values.recent_period == '') { - $('#error_period').show(); - return; - } + if (values.recent_period == '') { + $('#error_period').show(); + return; + } - setInfos({...values}); - }); + setInfos({ ...values }); + }); - $('#save_password').on('click', function() { - const passwords = { - password: $('#password').val(), - new_password: $('#password_new').val(), - conf_new_password: $('#password_conf').val(), - } - if (passwords.password == '' || passwords.new_password == '' || passwords.conf_new_password == '') { - $('#password-section input').each((i, element) => { - const el = $(element); - if (el.val() == '') { - el.parent().siblings().show(); - } - }); - return; - } - setInfos({...passwords}); - $('#password-section input').val(''); - }); + $('#reset_preferences').on('click', function () { + $('input[name="nb_image_page"]').val(user.nb_image_page); + $('select[name="theme"]').val(user.theme); + $('select[name="language"]').val(user.language); + $('input[name="recent_period"]').val(user.recent_period); + $('#opt_album').prop('checked', user.opt_album); + $('#opt_comment').prop('checked', user.opt_comment); + $('#opt_hits').prop('checked', user.opt_hits); + }); + + $('#default_preferences').on('click', function () { + $('input[name="nb_image_page"]').val(preferencesDefaultValues.nb_image_page); + $('input[name="recent_period"]').val(preferencesDefaultValues.recent_period); + $('#opt_album').prop('checked', preferencesDefaultValues.opt_album); + $('#opt_comment').prop('checked', preferencesDefaultValues.opt_comment); + $('#opt_hits').prop('checked', preferencesDefaultValues.opt_hits); + }); + } + + if (canUpdatePassword) { + $('#save_password').on('click', function () { + const passwords = { + password: $('#password').val(), + new_password: $('#password_new').val(), + conf_new_password: $('#password_conf').val(), + } + if (passwords.password == '' || passwords.new_password == '' || passwords.conf_new_password == '') { + $('#password-section input').each((i, element) => { + const el = $(element); + if (el.val() == '') { + el.parent().siblings().show(); + } + }); + return; + } + setInfos({ ...passwords }); + $('#password-section input').val(''); + }); + } + standardSaveSelector.forEach((selector, i) => { $(selector).on('click', function() { @@ -88,35 +113,6 @@ $(function() { }); }); - - const userDefaultValues = { - nb_image_page: $('input[name="nb_image_page"]').val(), - theme: $('select[name="theme"]').val(), - language: $('select[name="language"]').val(), - recent_period: $('input[name="recent_period"]').val(), - opt_album: $('#opt_album').is(':checked'), - opt_comment: $('#opt_comment').is(':checked'), - opt_hits: $('#opt_hits').is(':checked'), - } - - $('#reset_preferences').on('click', function() { - $('input[name="nb_image_page"]').val(userDefaultValues.nb_image_page); - $('select[name="theme"]').val(userDefaultValues.theme); - $('select[name="language"]').val(userDefaultValues.language); - $('input[name="recent_period"]').val(userDefaultValues.recent_period); - $('#opt_album').prop('checked', userDefaultValues.opt_album); - $('#opt_comment').prop('checked', userDefaultValues.opt_comment); - $('#opt_hits').prop('checked', userDefaultValues.opt_hits); - }); - - $('#default_preferences').on('click', function() { - $('input[name="nb_image_page"]').val(preferencesDefaultValues.nb_image_page); - $('input[name="recent_period"]').val(preferencesDefaultValues.recent_period); - $('#opt_album').prop('checked', preferencesDefaultValues.opt_album); - $('#opt_comment').prop('checked', preferencesDefaultValues.opt_comment); - $('#opt_hits').prop('checked', preferencesDefaultValues.opt_hits); - }); - // API KEY BELOW if (!can_manage_api) { $('.can-manage').hide(); @@ -202,24 +198,26 @@ function setInfos(params, method='pwg.users.setMyInfo', callback=null, errCallba data: all_params, success: (data) => { if (data.stat == 'ok') { + user = {...user, ...params}; if (typeof callback === 'function') { callback(data.result); return; }; pwgToaster({ text: data.result, icon: 'success' }); + return; } else if (data.stat == 'fail') { pwgToaster({ text: data.message, icon: 'error' }); } else { pwgToaster({ text: str_handle_error, icon: 'error' }); } - if (typeof callback === 'function') { + if (typeof errCallback === 'function') { errCallback(data); return; } }, error: function (e) { pwgToaster({ text: e.responseJSON?.message ?? str_handle_error, icon: 'error' }); - if (typeof callback === 'function') { + if (typeof errCallback === 'function') { errCallback(e); return; } @@ -237,7 +235,7 @@ function getAllApiKeys(reset = false) { }, success: function(res) { if (res.stat == 'ok') { - if (typeof res.result === 'string') { + if (typeof res.result === 'string' || res.result === false) { // No keys } else { AddApiLine(res.result, reset); diff --git a/themes/standard_pages/template/profile.tpl b/themes/standard_pages/template/profile.tpl index e83d2c9c6..f3a91c1d3 100644 --- a/themes/standard_pages/template/profile.tpl +++ b/themes/standard_pages/template/profile.tpl @@ -8,9 +8,23 @@ var selected_language = `{$language_options[$current_language]}`; var url_logo_dark = `{$ROOT_URL}themes/standard_pages/images/piwigo_logo_dark.svg`; {combine_script id='standard_pages_js' load='async' require='jquery' path='themes/standard_pages/js/standard_pages.js'} -{combine_script id='standard_profile_js' load='async' require='jquery' path='themes/standard_pages/js/profile.js'} +{combine_script id='standard_profile_js' load='footer' require='jquery' path='themes/standard_pages/js/profile.js'} {combine_script id='common' load='footer' require='jquery' path='admin/themes/default/js/common.js'} {footer_script} +let user = { + username: "{$USERNAME}", + email: "{$EMAIL}", + nb_image_page: $('input[name="nb_image_page"]').val(), + theme: $('select[name="theme"]').val(), + language: $('select[name="language"]').val(), + recent_period: $('input[name="recent_period"]').val(), + opt_album: $('#opt_album').is(':checked'), + opt_comment: $('#opt_comment').is(':checked'), + opt_hits: $('#opt_hits').is(':checked') +} + +const canUpdatePreferences = {if $ALLOW_USER_CUSTOMIZATION}true{else}false{/if}; +const canUpdatePassword = {if not $SPECIAL_USER}true{else}false{/if}; const standardSaveSelector = []; const preferencesDefaultValues = { nb_image_page: {$DEFAULT_USER_VALUES['nb_image_page']}, @@ -36,6 +50,7 @@ const str_revoke_key = "{'Do you really want to revoke the "%s" API key?'|transl const str_api_revoked = "{"API Key has been successfully revoked."|translate|escape:javascript}"; const str_api_edited = "{"API Key has been successfully edited."|translate|escape:javascript}"; const no_time_elapsed = "{"right now"|translate|escape:javascript}"; +const str_must_not_empty = "{'must not be empty'|translate|escape:javascript}"; {/footer_script} @@ -69,7 +84,7 @@ const no_time_elapsed = "{"right now"|translate|escape:javascript}";
-

{$USERNAME}

+

{$USERNAME}

diff --git a/ws.php b/ws.php index 98883c983..484e801c5 100644 --- a/ws.php +++ b/ws.php @@ -262,7 +262,6 @@ function ws_addDefaultMethods( $arr ) 'pwg.images.formats.searchImage', 'ws_images_formats_searchImage', array( - 'category_id' => array('type'=>WS_TYPE_ID, 'default'=>null), 'filename_list' => array(), ), 'Search for image ids matching the provided filenames. filename_list must be a JSON encoded associative array of unique_id:filename.

The method returns a list of unique_id:image_id.', @@ -500,6 +499,11 @@ function ws_addDefaultMethods( $arr ) 'type' => WS_TYPE_ID, 'info' => 'id of the extended image (name/category/level are not used if format_of is provided)', ), + 'update_mode' => array( + 'default' => false, + 'type' => WS_TYPE_BOOL, + 'info' => 'true if the update mode is active', + ), 'pwg_token' => array(), ), 'Add an image.