Skip to content

Commit

Permalink
feat: enable user to edit their preferred language and the country th…
Browse files Browse the repository at this point in the history
…ey live in (#8826)

The goal is to add, when signing up and in the user profile, a field for the user to inform their preferred language and the country they live. When signing up, the fields will be prefill with the country associated with the website (looking at the country code) the user uses and the same goes for the language.

The country list function is also cached and reused for country.pl

### Related issue(s) and discussion
- Fixes #8748 and #8749
  • Loading branch information
albatrousse authored Oct 26, 2023
1 parent a4eb678 commit 5839a9a
Show file tree
Hide file tree
Showing 17 changed files with 708 additions and 52 deletions.
38 changes: 11 additions & 27 deletions cgi/countries.pl
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,13 @@

use ProductOpener::Config qw/:all/;
use ProductOpener::Store qw/:all/;
use ProductOpener::Index qw/:all/;
use ProductOpener::Display qw/:all/;
use ProductOpener::Users qw/:all/;
use ProductOpener::URL qw/:all/;
use ProductOpener::Lang qw/:all/;
use ProductOpener::Tags qw/:all/;
use ProductOpener::Web qw/get_countries_options_list/;

use CGI qw/:cgi :form escapeHTML/;
use URI::Escape::XS;
use Storable qw/dclone/;
use Encode;
use JSON::PP;

Expand All @@ -47,30 +44,17 @@

my $term = decode utf8 => single_param('term');

my @options = @{get_countries_options_list($lang, undef)};
if (defined $term and $term ne '') {
# filter by term
@options = grep {$_->{label} =~ /$term/i} @options;
}
my %result = ();
foreach my $country (
sort {
( get_string_id_for_lang("no_language", $translations_to{countries}{$a}{$lang})
|| get_string_id_for_lang("no_language", $translations_to{countries}{$a}{'en'}))
cmp( get_string_id_for_lang("no_language", $translations_to{countries}{$b}{$lang})
|| get_string_id_for_lang("no_language", $translations_to{countries}{$b}{'en'}))
}
keys %{$properties{countries}}
)
{

my $cc = country_to_cc($country);
if (not(defined $cc)) {
next;
}

my $tag = display_taxonomy_tag($lang, 'countries', $country);
if ( (not defined $term)
or ($term eq '')
or ($tag =~ /$term/i))
{
$result{$cc} = $tag;
}
# transform to simple dict and use codes
foreach my $option (@options) {
my $code = country_to_cc($option->{value});
next if not defined $code;
$result{$code} = $option->{prefixed};
}

my $data = encode_json(\%result);
Expand Down
31 changes: 29 additions & 2 deletions cgi/user.pl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use ProductOpener::Store qw/:all/;
use ProductOpener::Index qw/:all/;
use ProductOpener::Display qw/:all/;
use ProductOpener::Web qw/:all/;
use ProductOpener::Users qw/:all/;
use ProductOpener::Lang qw/:all/;
use ProductOpener::Orgs qw/:all/;
Expand Down Expand Up @@ -169,6 +170,12 @@
$template_data_ref->{sections} = [];

if ($user_ref) {
my $selected_language = $user_ref->{preferred_language}
// (remove_tags_and_quote(single_param('preferred_language')) || "$lc");
my $selected_country = $user_ref->{country} // (remove_tags_and_quote(single_param('country')) || $country);
if ($selected_country eq "en:world") {
$selected_country = "";
}
push @{$template_data_ref->{sections}}, {
id => "user",
fields => [
Expand All @@ -193,6 +200,20 @@
type => "password",
label => "password_confirm"
},
{
field => "preferred_language",
type => "select",
label => "preferred_language",
selected => $selected_language,
options => get_languages_options_list($lc),
},
{
field => "country",
type => "select",
label => "select_country",
selected => $selected_country,
options => get_countries_options_list($lc),
},
{
# this is a honeypot to detect scripts, that fills every fields
# this one is hidden in a div and user won't see it
Expand All @@ -203,6 +224,10 @@
]
};

# news letter subscription value
my $newsletter = $user_ref->{newsletter} // (remove_tags_and_quote(single_param('newsletter')) || "on");
push @{$template_data_ref->{newsletter}}, $newsletter;

# Professional account
push @{$template_data_ref->{sections}},
{
Expand Down Expand Up @@ -257,6 +282,8 @@
}

# Contributor section
my $display_barcode = $user_ref->{display_barcode} || remove_tags_and_quote(single_param('display_barcode'));
my $edit_link = $user_ref->{edit_link} || remove_tags_and_quote(single_param('edit_link'));
my $contributor_section_ref = {
id => "contributor_settings",
name => lang("contributor_settings") . " (" . lang("optional") . ")",
Expand All @@ -266,13 +293,13 @@
field => "display_barcode",
type => "checkbox",
label => display_icon("barcode") . lang("display_barcode_in_search"),
value => $user_ref->{display_barcode} && "on",
value => $display_barcode && "on",
},
{
field => "edit_link",
type => "checkbox",
label => display_icon("edit") . lang("edit_link_in_search"),
value => $user_ref->{edit_link} && "on",
value => $edit_link && "on",
},
]
};
Expand Down
2 changes: 2 additions & 0 deletions lib/ProductOpener/TestDefaults.pm
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ A basic user.
team_1 => "",
team_2 => "",
team_3 => "",
preferred_language => "en",
country => "en:united-states",
action => "process",
type => "add"
);
Expand Down
7 changes: 6 additions & 1 deletion lib/ProductOpener/Users.pm
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,10 @@ sub check_user_form ($type, $user_ref, $errors_ref) {
$user_ref->{email} = $email;
}

# Country and preferred language
$user_ref->{preferred_language} = remove_tags_and_quote(single_param("preferred_language"));
$user_ref->{country} = remove_tags_and_quote(single_param("country"));

# Is there a checkbox to make a professional account
if (defined single_param("pro_checkbox")) {

Expand Down Expand Up @@ -684,7 +688,8 @@ sub process_user_form ($type, $user_ref, $request_ref) {

# Fetch the HTML mail template corresponding to the user language, english is the
# default if the translation is not available
my $email_content = get_html_email_content("user_welcome.html", $user_ref->{initial_lc});
my $language = $user_ref->{preferred_language} || $user_ref->{initial_lc};
my $email_content = get_html_email_content("user_welcome.html", $language);
my $user_name = $user_ref->{name};
# Replace placeholders by user values
$email_content =~ s/\{\{USERID\}\}/$userid/g;
Expand Down
69 changes: 64 additions & 5 deletions lib/ProductOpener/Web.pm
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ use ProductOpener::Images qw(:all);

use Template;
use Log::Log4perl;
use Unicode::Collate;

BEGIN {
use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS);
Expand All @@ -58,12 +59,15 @@ BEGIN {
&display_data_quality_description
&display_knowledge_panel
&get_languages_options_list
&get_countries_options_list
); #the fucntions which are called outside this file
%EXPORT_TAGS = (all => [@EXPORT_OK]);
}

use vars @EXPORT_OK;

my $unicode_collate = Unicode::Collate->new();

=head1 FUNCTIONS
=head2 display_field ( $product_ref, $field )
Expand Down Expand Up @@ -282,6 +286,9 @@ sub display_knowledge_panel ($product_ref, $panels_ref, $panel_id) {
return $html;
}

# cache for languages_options_list
my %lang_options_cache = ();

=head2 get_languages_options_list( $target_lc )
Generates a data structure containing the list of languages and their translation in a target language.
Expand All @@ -291,27 +298,79 @@ The data structured can be passed to HTML templates to construction a list of op

sub get_languages_options_list ($target_lc) {

return $lang_options_cache{$target_lc} if (defined $lang_options_cache{$target_lc});

my @lang_options = ();

my %lang_labels = ();
foreach my $l (@Langs) {
$lang_labels{$l} = display_taxonomy_tag($target_lc, 'languages', $language_codes{$l});
my $label = display_taxonomy_tag($target_lc, 'languages', $language_codes{$l});
# remove eventual language prefix
$label =~ s/^\w\w://;
$lang_labels{$l} = $label;
}

my @lang_values = sort {$lang_labels{$a} cmp $lang_labels{$b}} @Langs;
my @lang_values = sort {$unicode_collate->cmp($lang_labels{$a}, $lang_labels{$b})} @Langs;

foreach my $l (@lang_values) {
foreach my $lang_code (@lang_values) {

push(
@lang_options,
{
value => $l,
label => $lang_labels{$l},
value => $lang_code,
label => $lang_labels{$lang_code},
}
);
}

# cache
$lang_options_cache{$target_lc} = \@lang_options;

return \@lang_options;
}

# cache for get_countries
my %countries_options_lists = ();

=head2 get_countries_options_list( $target_lc )
Generates all the countries name in the $target_lc language suitable for an select option list
=head3 Arguments
=head4 $target_lc - language code for labels
=head4 $exclude_world - boolean to exclude 'World' from list
=head3 Return value
A reference to a list of hashes with every country code and their label in the $lc language
[{value => "fr", label => "France"},…]
=cut

sub get_countries_options_list ($target_lc, $exclude_world = 1) {
# if already computed send it back
if (defined $countries_options_lists{$target_lc}) {
return $countries_options_lists{$target_lc};
}
# compute countries list
my @countries_list = ();
my @tags_list = get_all_taxonomy_entries("countries");
foreach my $tag (@tags_list) {
next if $exclude_world and ($tag eq 'en:world');
my $country = display_taxonomy_tag($target_lc, "countries", $tag);
# remove eventual language prefix
my $country_no_code = $country;
$country_no_code =~ s/^\w\w://;
# Adding to the list the modified string
push @countries_list, {value => $tag, label => $country_no_code, prefixed => $country};
}
# sort by name
@countries_list = sort {$unicode_collate->cmp($a->{label}, $b->{label})} @countries_list;
# cache
$countries_options_lists{$target_lc} = \@countries_list;
return \@countries_list;
}

1;
6 changes: 5 additions & 1 deletion po/common/common.pot
Original file line number Diff line number Diff line change
Expand Up @@ -6595,6 +6595,10 @@ msgctxt "relative_to_all_products"
msgid "relative to all products"
msgstr "relative to all products"

msgctxt "preferred_language"
msgid "Preferred Language"
msgstr "Preferred Language"

msgctxt "packagings_n_p"
msgid "Numbers of packaging components"
msgstr "Numbers of packaging components"
Expand All @@ -6617,4 +6621,4 @@ msgstr "Weight per 100g of product"

msgctxt "weight_percent"
msgid "Weight percent"
msgstr "Weight percent"
msgstr "Weight percent"
4 changes: 4 additions & 0 deletions po/common/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -4699,6 +4699,10 @@ msgctxt "preference_mandatory"
msgid "Mandatory"
msgstr "Mandatory"

msgctxt "preferred_language"
msgid "Preferred Language"
msgstr "Preferred Language"

msgctxt "packaging_alt"
msgid "Recycling instructions and/or packaging information"
msgstr "Recycling instructions and/or packaging information"
Expand Down
4 changes: 4 additions & 0 deletions po/common/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -4697,6 +4697,10 @@ msgctxt "preference_mandatory"
msgid "Mandatory"
msgstr "Obligatoire"

msgctxt "preferred_language"
msgid "Preferred Language"
msgstr "Langue préférée"

msgctxt "packaging_alt"
msgid "Recycling instructions and/or packaging information"
msgstr "Instruction de recyclage et/ou informations d'emballage"
Expand Down
13 changes: 10 additions & 3 deletions templates/web/pages/user_form/user_form_page.tt.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@
[% ELSIF field.type == 'email' %]
<label for="[% field.field %]"> [% field.label %]</label>
<input type="email" id="[% field.field %]" name="[% field.field %]" value = "[% field.value %]" autocomplete="email"/>
[% ELSIF field.type == 'select' %]
<label for="[% field.field %]"> [% lang(field.label) %]</label>
<select name="[% field.field %]" id="[% field.field %]-select">
<option value=""></option>
[% FOREACH item IN field.options %]
<option value="[% item.value %]" [% IF item.value == field.selected %]selected[% END %]> [% item.label %]</option>
[% END %]
</select>
[% ELSIF field.type == 'checkbox' %]
[% IF accepted_organization && section.id == "professional" %]
<p>[% f_lang('f_this_is_a_pro_account_for_org', {'org' => accepted_organization}) %]</p>
Expand All @@ -108,7 +116,6 @@
<input type="hidden" name="[% field.field %]" value="[% field.value %]">
</label>
[% END %]

[% END %]
</div>
<!-- show video about account creation beside user form section-->
Expand Down Expand Up @@ -146,13 +153,13 @@
<!--newsletter subscription-->
[% IF type == 'add' %]
<label>
<input type="checkbox" name="newsletter" checked="on" />
<input type="checkbox" name="newsletter" [% IF newsletter == "on" %]checked="on"[% END %] />
[% lang("newsletter_description") %]
</label>
<p>[% lang("unsubscribe_info") %]</p>
[% END %]

[% IF userid %]
[% IF userid and (type != 'add') %]
<fieldset>
<legend class="text_warning">[% lang("danger_zone") %]</legend>
<input type="button" name=".delete" class="button text_warning" value="[% lang("delete_user") %]" onclick="if ('[% userid %]'.localeCompare(prompt('[% lang("delete_confirmation") %]'),undefined,'base') === 0) {document.getElementById('delete_input').value='on';document.getElementById('user_form').submit();}"/>
Expand Down
31 changes: 31 additions & 0 deletions tests/integration/countries.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/perl -w

use ProductOpener::PerlStandards;

use Test::More;
use ProductOpener::APITest qw/:all/;

wait_application_ready();

my $tests_ref = [
{
test_case => 'list-countries-world',
path => '/cgi/countries.pl',
expected_type => "json",
},
{
test_case => 'list-countries-french',
subdomain => 'fr',
path => '/cgi/countries.pl',
expected_type => "json",
},
{
test_case => 'list-countries-filtered-fr',
path => '/cgi/countries.pl?term=FR',
expected_type => "json",
}
];

execute_api_tests(__FILE__, $tests_ref);

done_testing();
Loading

0 comments on commit 5839a9a

Please sign in to comment.