diff --git a/cgi/product.pl b/cgi/product.pl
deleted file mode 100755
index 374b4901d7a3f..0000000000000
--- a/cgi/product.pl
+++ /dev/null
@@ -1,1536 +0,0 @@
-#!/usr/bin/perl -w
-
-# This file is part of Product Opener.
-#
-# Product Opener
-# Copyright (C) 2011-2021 Association Open Food Facts
-# Contact: contact@openfoodfacts.org
-# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France
-#
-# Product Opener is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see
" . $tt->error() . "
"; - -my @fields = @ProductOpener::Config::product_fields; - -if ($admin) { - push @fields, "environment_impact_level"; - - # Let admins edit any other fields - if (defined single_param("fields")) { - push @fields, split(/,/, single_param("fields")); - } - -} - -if (($action eq 'process') and (($type eq 'add') or ($type eq 'edit'))) { - - # Process edit rules - - $log->debug("phase 0 - checking edit rules", {code => $code, type => $type}) if $log->is_debug(); - - my $proceed_with_edit = process_product_edit_rules($product_ref); - - $log->debug("phase 0", {code => $code, type => $type, proceed_with_edit => $proceed_with_edit}) if $log->is_debug(); - - if (not $proceed_with_edit) { - - display_error("Edit against edit rules", 403); - } - - $log->debug("phase 1", {code => $code, type => $type}) if $log->is_debug(); - - exists $product_ref->{new_server} and delete $product_ref->{new_server}; - - # 26/01/2017 - disallow barcode changes until we fix bug #677 - if ($User{moderator} and (defined single_param("new_code")) and (single_param("new_code") ne "")) { - - change_product_server_or_code($product_ref, single_param("new_code"), \@errors); - $code = $product_ref->{code}; - } - - my @param_fields = (); - - my @param_sorted_langs = (); - my %param_sorted_langs = (); - if (defined single_param("sorted_langs")) { - foreach my $display_lc (split(/,/, single_param("sorted_langs"))) { - if ($display_lc =~ /^\w\w$/) { - push @param_sorted_langs, $display_lc; - $param_sorted_langs{$display_lc} = 1; - } - } - } - else { - push @param_sorted_langs, $product_ref->{lc}; - } - - # Make sure we have the main language of the product (which could be new) - # needed if we are moving data from one language to the main language - if ( (defined single_param("lang")) - and (single_param("lang") =~ /^\w\w$/) - and (not defined $param_sorted_langs{single_param("lang")})) - { - push @param_sorted_langs, single_param("lang"); - } - - $product_ref->{"debug_param_sorted_langs"} = \@param_sorted_langs; - - foreach my $field ('product_name', 'generic_name', @fields, 'nutrition_data_per', 'nutrition_data_prepared_per', - 'serving_size', 'allergens', 'traces', 'ingredients_text', 'origin', 'packaging_text', 'lang') - { - - if (defined $language_fields{$field}) { - foreach my $display_lc (@param_sorted_langs) { - push @param_fields, $field . "_" . $display_lc; - } - } - else { - push @param_fields, $field; - } - } - - # Move all data and photos from one language to another? - if ($User{moderator}) { - - my $product_lc = single_param("lang"); - - foreach my $from_lc (@param_sorted_langs) { - - my $moveid = "move_" . $from_lc . "_data_and_images_to_main_language"; - - if (($from_lc ne $product_lc) and (defined single_param($moveid)) and (single_param($moveid) eq "on")) { - - my $mode = single_param($moveid . "_mode") || "replace"; - - $log->debug( - "moving all data and photos from one language to another", - {from_lc => $from_lc, product_lc => $product_lc, mode => $mode} - ) if $log->is_debug(); - - # Text fields - - foreach my $field (sort keys %language_fields) { - - my $from_field = $field . "_" . $from_lc; - my $to_field = $field . "_" . $product_lc; - - my $from_value = single_param($from_field); - - $log->debug("moving field value?", - {from_field => $from_field, from_value => $from_value, to_field => $to_field}) - if $log->is_debug(); - - if ((defined $from_value) and ($from_value ne "")) { - - my $to_value = single_param($to_field); - - $log->debug( - "moving field value", - { - from_field => $from_field, - from_value => $from_value, - to_field => $to_field, - to_value => $to_value, - mode => $mode - } - ) if $log->is_debug(); - - if (($mode eq "replace") or ((not defined $to_value) or ($to_value eq ""))) { - - $log->debug( - "replacing to field value", - { - from_field => $from_field, - from_value => $from_value, - to_field => $to_field, - to_value => $to_value, - mode => $mode - } - ) if $log->is_debug(); - - param($to_field, $from_value); - } - - $log->debug( - "deleting from field value", - { - from_field => $from_field, - from_value => $from_value, - to_field => $to_field, - to_value => $to_value, - mode => $mode - } - ) if $log->is_debug(); - - param($from_field, ""); - } - } - - # Selected photos - - foreach my $imageid ("front", "ingredients", "nutrition", "packaging") { - - my $from_imageid = $imageid . "_" . $from_lc; - my $to_imageid = $imageid . "_" . $product_lc; - - if ((defined $product_ref->{images}) and (defined $product_ref->{images}{$from_imageid})) { - - $log->debug("moving selected image", {from_imageid => $from_imageid, to_imageid => $to_imageid}) - if $log->is_debug(); - - if (($mode eq "replace") or (not defined $product_ref->{images}{$to_imageid})) { - - $product_ref->{images}{$to_imageid} = $product_ref->{images}{$from_imageid}; - my $rev = $product_ref->{images}{$from_imageid}{rev}; - - # Rename the images - - my $path = product_path($product_ref); - - foreach my $max ($thumb_size, $small_size, $display_size, "full") { - my $from_file - = "$www_root/images/products/$path/" - . $from_imageid . "." - . $rev . "." - . $max . ".jpg"; - my $to_file - = "$www_root/images/products/$path/" . $to_imageid . "." . $rev . "." . $max . ".jpg"; - File::Copy::move($from_file, $to_file); - } - } - - delete $product_ref->{images}{$from_imageid}; - } - } - } - } - } - - foreach my $field (@param_fields) { - - if (defined single_param($field)) { - - # If we are on the public platform, and the field data has been imported from the producer platform - # ignore the field changes for non tag fields, unless made by a moderator - if ( ((not defined $server_options{private_products}) or (not $server_options{private_products})) - and (defined $product_ref->{owner_fields}) - and (defined $product_ref->{owner_fields}{$field}) - and (not $User{moderator})) - { - $log->debug( - "skipping field with a value set by the owner", - { - code => $code, - field_name => $field, - existing_field_value => $product_ref->{$field}, - new_field_value => remove_tags_and_quote(decode utf8 => single_param($field)) - } - ) if $log->is_debug(); - } - - if ($field eq "lang") { - my $value = remove_tags_and_quote(decode utf8 => single_param($field)); - - # strip variants fr-BE fr_BE - $value =~ s/^([a-z][a-z])(-|_).*$/$1/i; - $value = lc($value); - - # skip unrecognized languages (keep the existing lang & lc value) - if (defined $lang_lc{$value}) { - $product_ref->{lang} = $value; - $product_ref->{lc} = $value; - } - - } - else { - # infocards set by admins can contain HTML - if (($admin) and ($field =~ /infocard/)) { - $product_ref->{$field} = decode utf8 => single_param($field); - } - else { - $product_ref->{$field} = remove_tags_and_quote(decode utf8 => single_param($field)); - } - } - - $log->debug("before compute field_tags", - {code => $code, field_name => $field, field_value => $product_ref->{$field}}) - if $log->is_debug(); - if ($field =~ /ingredients_text/) { - # the ingredients_text_with_allergens[_$lc] will be recomputed after - my $ingredients_text_with_allergens = $field; - $ingredients_text_with_allergens =~ s/ingredients_text/ingredients_text_with_allergens/; - delete $product_ref->{$ingredients_text_with_allergens}; - } - - compute_field_tags($product_ref, $lc, $field); - - } - else { - $log->debug("could not find field in params", {field => $field}) if $log->is_debug(); - } - } - - if ( (defined $product_ref->{nutriments}{"carbon-footprint"}) - and ($product_ref->{nutriments}{"carbon-footprint"} ne '')) - { - push @{$product_ref->{"labels_hierarchy"}}, "en:carbon-footprint"; - push @{$product_ref->{"labels_tags"}}, "en:carbon-footprint"; - } - - if ((defined $product_ref->{nutriments}{"glycemic-index"}) and ($product_ref->{nutriments}{"glycemic-index"} ne '')) - { - push @{$product_ref->{"labels_hierarchy"}}, "en:glycemic-index"; - push @{$product_ref->{"labels_tags"}}, "en:glycemic-index"; - } - - # For fields that can have different values in different languages, copy the main language value to the non suffixed field - - foreach my $field (keys %language_fields) { - if ($field !~ /_image/) { - if (defined $product_ref->{$field . "_$product_ref->{lc}"}) { - $product_ref->{$field} = $product_ref->{$field . "_$product_ref->{lc}"}; - } - } - } - - $log->debug("compute_languages") if $log->is_debug(); - - compute_languages($product_ref); # need languages for allergens detection and cleaning ingredients - $log->debug("clean_ingredients") if $log->is_debug(); - - # Ingredients classes - clean_ingredients_text($product_ref); - $log->debug("extract_ingredients_from_text") if $log->is_debug(); - extract_ingredients_from_text($product_ref); - $log->debug("extract_ingredients_classes_from_text") if $log->is_debug(); - extract_ingredients_classes_from_text($product_ref); - $log->debug("detect_allergens_from_text") if $log->is_debug(); - detect_allergens_from_text($product_ref); - compute_carbon_footprint_from_ingredients($product_ref); - compute_carbon_footprint_from_meat_or_fish($product_ref); - - # Food category rules for sweetened/sugared beverages - # French PNNS groups from categories - - if ((defined $options{product_type}) and ($options{product_type} eq "food")) { - $log->debug("Food::special_process_product") if $log->is_debug(); - ProductOpener::Food::special_process_product($product_ref); - } - - # Obsolete products - - if (($User{moderator} or $Owner_id) and (defined single_param('obsolete_since_date'))) { - $product_ref->{obsolete} = remove_tags_and_quote(decode utf8 => single_param("obsolete")); - $product_ref->{obsolete_since_date} = remove_tags_and_quote(decode utf8 => single_param("obsolete_since_date")); - } - - # Update the nutrients - - assign_nutriments_values_from_request_parameters($product_ref, $nutriment_table); - - # product check - - if ($User{moderator}) { - - my $checked = remove_tags_and_quote(decode utf8 => single_param("photos_and_data_checked")); - if ((defined $checked) and ($checked eq 'on')) { - if ((defined $product_ref->{checked}) and ($product_ref->{checked} eq 'on')) { - my $rechecked = remove_tags_and_quote(decode utf8 => single_param("photos_and_data_rechecked")); - if ((defined $rechecked) and ($rechecked eq 'on')) { - $product_ref->{last_checker} = $User_id; - $product_ref->{last_checked_t} = time(); - } - } - else { - $product_ref->{checked} = 'on'; - $product_ref->{last_checker} = $User_id; - $product_ref->{last_checked_t} = time(); - } - } - else { - delete $product_ref->{checked}; - delete $product_ref->{last_checker}; - delete $product_ref->{last_checked_t}; - } - - } - - # Compute nutrition data per 100g and per serving - - $log->debug("compute nutrition data") if $log->is_debug(); - - $log->trace("compute_serving_size_date - start") if $log->is_trace(); - - fix_salt_equivalent($product_ref); - - compute_serving_size_data($product_ref); - - compute_nutrition_score($product_ref); - - compute_nova_group($product_ref); - - compute_nutrient_levels($product_ref); - - compute_unknown_nutrients($product_ref); - - # Until we provide an interface to directly change the packaging data structure - # erase it before reconstructing it - # (otherwise there is no way to remove incorrect entries) - $product_ref->{packagings} = []; - - analyze_and_combine_packaging_data($product_ref); - - if ((defined $options{product_type}) and ($options{product_type} eq "food")) { - compute_ecoscore($product_ref); - compute_forest_footprint($product_ref); - } - - ProductOpener::DataQuality::check_quality($product_ref); - - $log->trace("end compute_serving_size_date - end") if $log->is_trace(); - - if ($#errors >= 0) { - $action = 'display'; - } -} - -# Display the product edit form - -my %remember_fields = ('purchase_places' => 1, 'stores' => 1); - -# Display each field - -sub display_input_field ($product_ref, $field, $language) { - # $field can be in %language_fields and suffixed by _[lc] - - my $fieldtype = $field; - my $display_lc = $lc; - my @field_notes; - - my $template_data_ref_field = {}; - - if (($field =~ /^(.*?)_(..|new_lc)$/) and (defined $language_fields{$1})) { - $fieldtype = $1; - $display_lc = $2; - } - - my $autocomplete = ""; - my $class = ""; - if (defined $tags_fields{$fieldtype}) { - $class = "tagify-me"; - if ((defined $taxonomy_fields{$fieldtype}) or ($fieldtype eq 'emb_codes')) { - $autocomplete = "$formatted_subdomain/cgi/suggest.pl?tagtype=$fieldtype&"; - } - } - - my $value = $product_ref->{$field}; - - if ( - (defined $value) - and (defined $taxonomy_fields{$field}) - # if the field was previously not taxonomized, the $field_hierarchy field does not exist - and (defined $product_ref->{$field . "_hierarchy"}) - ) - { - $value = display_tags_hierarchy_taxonomy($lc, $field, $product_ref->{$field . "_hierarchy"}); - # Remove tags - $value =~ s/<(([^>]|\n)*)>//g; - } - if (not defined $value) { - $value = ""; - } - - $template_data_ref_field->{language} = $language; - $template_data_ref_field->{field} = $field; - $template_data_ref_field->{class} = $class; - $template_data_ref_field->{value} = $value; - $template_data_ref_field->{display_lc} = $display_lc; - $template_data_ref_field->{autocomplete} = $autocomplete; - $template_data_ref_field->{fieldtype} = $Lang{$fieldtype}{$lang}; - - my $html_field = ''; - - if (($field =~ /infocard/) or ($field =~ /^packaging_text/)) { - - } - else { - # Line feeds will be removed in text inputs, convert them to spaces - $value =~ s/\n/ /g; - } - - foreach my $note ("_note", "_note_2") { - if (defined $Lang{$fieldtype . $note}{$lang}) { - - push( - @field_notes, - { - note => $Lang{$fieldtype . $note}{$lang}, - } - ); - - } - } - - $template_data_ref_field->{field_notes} = \@field_notes; - - if (defined $Lang{$fieldtype . "_example"}{$lang}) { - - my $examples = $Lang{example}{$lang}; - if ($Lang{$fieldtype . "_example"}{$lang} =~ /,/) { - $examples = $Lang{examples}{$lang}; - } - $template_data_ref_field->{examples} = $examples; - $template_data_ref_field->{field_type_examples} = $Lang{$fieldtype . "_example"}{$lang}; - } - - process_template('web/pages/product_edit/display_input_field.tt.html', $template_data_ref_field, \$html_field) - or $html_field = "" . $tt->error() . "
"; - - return $html_field; -} - -if (($action eq 'display') and (($type eq 'add') or ($type eq 'edit'))) { - - # Populate the energy-kcal or energy-kj field from the energy field if it exists - compute_serving_size_data($product_ref); - - my $template_data_ref_display = {}; - my $js; - - $log->debug("displaying product", {code => $code}) if $log->is_debug(); - - # Lang strings for product.js - - my $moderator = 0; - if ($User{moderator}) { - $moderator = 1; - } - - $header .= <