From 1f7f44bac8c898c5a6f325503f8dad78c120ef2b Mon Sep 17 00:00:00 2001
From: HWFord <54360213+HWFord@users.noreply.github.com>
Date: Mon, 30 Jun 2025 15:25:15 +0200
Subject: [PATCH 01/15] relates #2377 check new sizes are bigger than xxl
if the default sizes aren't bigger than the XXL then multiply the XXL size by 1.5 for 3XL and the 3XL size by 1.5 for 4XL
---
install/db/177-database.php | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
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'];
From 5e2251dff8840783234a1eecf657cb7d006717e6 Mon Sep 17 00:00:00 2001
From: Linty
Date: Wed, 2 Jul 2025 15:42:29 +0200
Subject: [PATCH 02/15] fixes #2383 add custom ellipsis to show end of long
album names
---
admin/themes/default/css/components/album_selector.css | 5 +++++
admin/themes/default/js/album_selector.js | 6 +++++-
2 files changed, 10 insertions(+), 1 deletion(-)
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
-----------*/
From eec9a919a5d9435cfb2916ea6ea755d0da6b7d8b Mon Sep 17 00:00:00 2001
From: Linty
Date: Mon, 7 Jul 2025 08:58:27 +0200
Subject: [PATCH 03/15] issue #2355 enforce ui context for API key management
...and improve profile JS. Replaces can_manage_api_key() with connected_with_pwg_ui() to ensure API key management is only allowed from UI logins, and sets 'connected_with' in session during auto-login. Refactors profile.js to respect canUpdatePreferences and canUpdatePassword, moves user state initialization to template, and improves preference reset/default logic. Also adjusts script loading and minor UI details in profile.tpl.
---
include/functions_user.inc.php | 22 ++++
include/ws_functions/pwg.users.php | 18 +--
themes/standard_pages/js/profile.js | 140 ++++++++++-----------
themes/standard_pages/template/profile.tpl | 19 ++-
4 files changed, 112 insertions(+), 87 deletions(-)
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.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/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}";
From e28360fc5b19be912cbf6c42d9b91fe00371701e Mon Sep 17 00:00:00 2001
From: Linty
Date: Mon, 7 Jul 2025 09:53:48 +0200
Subject: [PATCH 04/15] fixes #2388 hide update actions for dev mode extensions
---
admin/updates_ext.php | 6 ++++++
1 file changed, 6 insertions(+)
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'], '>='))
From d613149dfd8e11007164d7cd65bced3fd57d2505 Mon Sep 17 00:00:00 2001
From: Linty
Date: Mon, 7 Jul 2025 10:25:00 +0200
Subject: [PATCH 05/15] fixes #2389 filter user activity by object and fix csv
export
---
admin/user_activity.php | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/admin/user_activity.php b/admin/user_activity.php
index d85a17592..dd31df050 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);
From b6459958f02b42a0c86bfca273e42f9f132d0c5a Mon Sep 17 00:00:00 2001
From: HWFord <54360213+HWFord@users.noreply.github.com>
Date: Fri, 11 Jul 2025 07:55:35 +0200
Subject: [PATCH 06/15] relates #2377 fix typo
---
language/en_UK/common.lang.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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';
From 798f30ea515a25214c385733960f5cbc804f8b1a Mon Sep 17 00:00:00 2001
From: plegall
Date: Tue, 15 Jul 2025 14:14:42 +0200
Subject: [PATCH 07/15] issue #2390 avoid generating duplicate paths during
upload
---
admin/include/functions_upload.inc.php | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/admin/include/functions_upload.inc.php b/admin/include/functions_upload.inc.php
index b5eb59b35..3ea729e1b 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))
From a7c735a14b5d3c3b1e077ed550e047f224bd8a03 Mon Sep 17 00:00:00 2001
From: HWFord <54360213+HWFord@users.noreply.github.com>
Date: Wed, 16 Jul 2025 15:07:00 +0200
Subject: [PATCH 08/15] relates #2306 move button for update page
---
admin/themes/default/template/updates_pwg.tpl | 60 ++++++++++++++-----
1 file changed, 46 insertions(+), 14 deletions(-)
diff --git a/admin/themes/default/template/updates_pwg.tpl b/admin/themes/default/template/updates_pwg.tpl
index 0fda39457..0f94e1bf3 100644
--- a/admin/themes/default/template/updates_pwg.tpl
+++ b/admin/themes/default/template/updates_pwg.tpl
@@ -110,14 +110,29 @@ p.release .errors {margin:0}
{'This is a minor update, with only bug corrections.'|@translate}
{/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}
-
- {'Update in progress...'|@translate}

-
-
+
+
+
+
+
{/if}
From 4c872681798f86b19111778341ade240628490cc Mon Sep 17 00:00:00 2001
From: HWFord <54360213+HWFord@users.noreply.github.com>
Date: Tue, 22 Jul 2025 11:05:37 +0200
Subject: [PATCH 09/15] relates #2377 fix php warning in maintenence
if the 3XL and 4xl size don't exist it creates a php warning. this avoids the warning because theses sizes haven't yet been generated so don't exist
---
admin/themes/default/template/maintenance_actions.tpl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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}
From 4f6da8ea6a906b7ab8e272f0fe45222437e3f850 Mon Sep 17 00:00:00 2001
From: Martin Raby
Date: Tue, 22 Jul 2025 13:00:27 +0200
Subject: [PATCH 10/15] Relates #2351 : To see the newsletter promote, the
account must have 2 weeks ancient, 3 albums created and 30 photos uploaded
---
admin/intro.php | 42 +++++++++++++++++++++++++++++++++++-------
1 file changed, 35 insertions(+), 7 deletions(-)
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']),
+ )
+ );
+ }
+
}
From 5f0dc8548f7abe6385d9b8b59e1048bd7fadffdd Mon Sep 17 00:00:00 2001
From: Linty
Date: Tue, 22 Jul 2025 18:26:01 +0200
Subject: [PATCH 11/15] issue #2355 update piwigo structure sql
and fix api key bug after first install
---
install.php | 1 +
install/piwigo_structure-mysql.sql | 5 +++++
2 files changed, 6 insertions(+)
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/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;
From fd54a917be23b65e1df3490bb995a9c5726fd587 Mon Sep 17 00:00:00 2001
From: plegall
Date: Wed, 23 Jul 2025 17:24:52 +0200
Subject: [PATCH 12/15] hide whatsnew 16 for now
---
admin.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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,
)
);
From bc4acec569ca5629deba162a9931a72d9adc77e1 Mon Sep 17 00:00:00 2001
From: plegall
Date: Thu, 24 Jul 2025 21:29:38 +0200
Subject: [PATCH 13/15] issue #2390 warn about duplicate paths
---
admin/include/functions.php | 24 ++++++++++++++++++++++++
language/en_UK/admin.lang.php | 1 +
language/fr_FR/admin.lang.php | 1 +
3 files changed, 26 insertions(+)
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/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/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
From d0ac05d951fee342c05c11364279b015ddbd48f1 Mon Sep 17 00:00:00 2001
From: Perrom <107625315+Perrom@users.noreply.github.com>
Date: Thu, 31 Jul 2025 10:41:01 +0200
Subject: [PATCH 14/15] fixes #2353 update images with upload form (#2385)
Add a mode to update photos with the upload form. Modify the upload formats form, so that it will update the photo with the same file extension.
---
admin/include/functions_upload.inc.php | 34 +++-
admin/photos_add_direct.php | 7 +-
admin/themes/clear/theme.css | 10 ++
admin/themes/default/js/photos_add_direct.js | 151 ++++++++++++++++--
.../default/template/photos_add_direct.tpl | 59 +++++--
admin/themes/default/theme.css | 79 +++++++++
admin/themes/roma/theme.css | 15 +-
include/ws_functions/pwg.images.php | 62 ++++++-
ws.php | 6 +-
9 files changed, 385 insertions(+), 38 deletions(-)
diff --git a/admin/include/functions_upload.inc.php b/admin/include/functions_upload.inc.php
index 3ea729e1b..46e0fdaf9 100644
--- a/admin/include/functions_upload.inc.php
+++ b/admin/include/functions_upload.inc.php
@@ -520,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));
@@ -530,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/photos_add_direct.php b/admin/photos_add_direct.php
index e66fbbbc7..ea6cdd2f2 100644
--- a/admin/photos_add_direct.php
+++ b/admin/photos_add_direct.php
@@ -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 cc5a09900..4fd1c5ef5 100644
--- a/admin/themes/clear/theme.css
+++ b/admin/themes/clear/theme.css
@@ -665,6 +665,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;}
@@ -714,6 +717,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/js/photos_add_direct.js b/admin/themes/default/js/photos_add_direct.js
index 975052ff8..4d361f5d7 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( '- ' + infoTextAdd + ', ' + infoTextUpdate + '
');
+ }
+ else
+ {
+ const infoText = addedPhotos.length ? infoTextAdd : infoTextUpdate;
+ $(".infos").append('');
+ }
if (!formatMode) {
html = sprintf(
@@ -357,7 +491,6 @@ $(function () {
}
}
});
-
});
/*--------------
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}