Skip to content

Commit

Permalink
Merge branch 'main' into feat/add-missing-intermediate-nutriment
Browse files Browse the repository at this point in the history
  • Loading branch information
teolemon authored Dec 22, 2024
2 parents 5e1de32 + ac175ce commit 4aab18e
Show file tree
Hide file tree
Showing 1,158 changed files with 46,526 additions and 30,866 deletions.
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ logs
debug/
perl-language-server.log

# tests outputs
tests/unit/outputs/
tests/integration/outputs/

openfoodfacts-mongodbdump.tar.gz
openfoodfacts-products.jsonl.gz
en.openfoodfacts.org.products.csv
Expand Down
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ ODOO_CRM_PASSWORD=

# No need to have it running, it's just to compose a URL
# you might also use the .net service
NUTRIPATROL_URL=nutripatrol.localhost
NUTRIPATROL_URL=http://nutripatrol.localhost

BUILD_CACHE_REPO=openfoodfacts/openfoodfacts-build-cache

Expand Down
35 changes: 18 additions & 17 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ WikiData:
# Tracking issue: https://github.com/openfoodfacts/openfoodfacts-server/issues/5555
📸 Open Products Facts:
- changed-files:
- any-glob-to-any-file: 'taxonomies/products/**/*'
- any-glob-to-any-file: 'taxonomies/product/**/*'
- any-glob-to-any-file: 'po/openproductsfacts/**/*'
- any-glob-to-any-file: 'lib/ProductOpener/Config_opf.pm'
- any-glob-to-any-file: 'conf/nginx/sites-available/opf'
Expand Down Expand Up @@ -505,21 +505,6 @@ Data import:
- any-glob-to-any-file: 'cgi/generate_sample_import_file.pl'

# https://openfoodfacts.github.io/openfoodfacts-server/dev/ref-perl-pod/ProductOpener/Ingredients.html
🥗 Ingredients:
- changed-files:
- any-glob-to-any-file: 'lib/ProductOpener/Ingredients.pm'
- any-glob-to-any-file: 'taxonomies/food/ingredients.txt'
- any-glob-to-any-file: 'tests/unit/ingredients.t'
- any-glob-to-any-file: 'tests/unit/ingredients_analysis.t'
- any-glob-to-any-file: 'tests/unit/ingredients_clean.t'
- any-glob-to-any-file: 'tests/unit/ingredients_nesting.t'
- any-glob-to-any-file: 'tests/unit/ingredients_parsing.t'
- any-glob-to-any-file: 'tests/unit/ingredients_parsing_todo.t'
- any-glob-to-any-file: 'tests/unit/ingredients_percent.t'
- any-glob-to-any-file: 'tests/unit/ingredients_processing.t'
- any-glob-to-any-file: 'tests/unit/ingredients_tags.t'
- any-glob-to-any-file: 'scripts/test_ingredient_parser.pl'

# We want to improve the analysis of ingredient list to extract ingredients and their properties, across languages.
# This is helpful to determine if a product is vegan, vegetarian, contains palm oil, is kosher/halal, the exact Nutri-Score, how much environmental impact it has…
# https://wiki.openfoodfacts.org/Ingredients_Extraction_and_Analysis
Expand All @@ -538,7 +523,23 @@ Data import:
- any-glob-to-any-file: 'scripts/extract_individual_ingredients.pl'
- any-glob-to-any-file: 'scripts/aggregate_ingredients.pl'
- any-glob-to-any-file: 'lib/ProductOpener/Ingredients.pm'

- any-glob-to-any-file: 'tests/unit/ingredients_parsing.t'
- any-glob-to-any-file: 'lib/ProductOpener/Ingredients.pm'
- any-glob-to-any-file: 'taxonomies/food/ingredients.txt'
- any-glob-to-any-file: 'tests/unit/ingredients.t'
- any-glob-to-any-file: 'tests/unit/ingredients_analysis.t'
- any-glob-to-any-file: 'tests/unit/ingredients_clean.t'
- any-glob-to-any-file: 'tests/unit/ingredients_nesting.t'
- any-glob-to-any-file: 'tests/unit/ingredients_parsing_todo.t'
- any-glob-to-any-file: 'tests/unit/ingredients_percent.t'
- any-glob-to-any-file: 'tests/unit/ingredients_processing.t'
- any-glob-to-any-file: 'tests/unit/ingredients_tags.t'
- any-glob-to-any-file: 'scripts/test_ingredient_parser.pl'
- any-glob-to-any-file: 'tests/unit/expected_test_results/ingredients/en-category-types.json'
- any-glob-to-any-file: 'tests/unit/expected_test_results/ingredients/fr-infinite-loop-allergens.json'
- any-glob-to-any-file: 'tests/unit/expected_test_results/ingredients/fr-marmelade.json'
- any-glob-to-any-file: 'tests/unit/expected_test_results/ingredients/fr-percents-origins-2.json'
- any-glob-to-any-file: 'tests/unit/expected_test_results/ingredients/ru-russian-oil.json'
# Labels are all claims present on product packages.
# https://wiki.openfoodfacts.org/Labels
# Tracking issue:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/generate-doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
- name: Deploy API documentation to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4.6.9
uses: JamesIves/github-pages-deploy-action@v4.7.2
# we only deploy on push to main
if: |
github.event_name == 'push' && github.event.ref == 'refs/heads/main'
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,17 @@ jobs:
key: taxonomies-${{ hashFiles('taxonomies/**') }}
restore-keys: taxonomies-
- name: Download backend image from artifacts
id: downloadbackendimage
uses: ishworkh/[email protected]
with:
image: "openfoodfacts-server/backend:dev"
# downloadbackendimage task loads the image into docker and keeps the original file.
# As our runs tend to hit the storage limits for GitHub Actions, manually delete the
# downloaded file for now. It's not needed after being loaded into docker.
- name: Remove downloaded image
env:
FILE: "${{ steps.downloadbackendimage.outputs.download_path }}"
run: rm $FILE
- name: build taxonomies (should use cache)
run: make DOCKER_LOCAL_DATA="$(pwd)" build_taxonomies GITHUB_TOKEN="${{ secrets.TAXONOMY_CACHE_GITHUB_TOKEN }}"
- name: check taxonomies
Expand Down Expand Up @@ -148,9 +156,17 @@ jobs:
run: |
git ls-files taxonomies/ | xargs -I{} git log -1 --date=format:%Y%m%d%H%M.%S --format='touch -t %ad "{}"' "{}" | bash
- name: Download backend image from artifacts
id: downloadbackendimage
uses: ishworkh/[email protected]
with:
image: "openfoodfacts-server/backend:dev"
# downloadbackendimage task loads the image into docker and keeps the original file.
# As our runs tend to hit the storage limits for GitHub Actions, manually delete the
# downloaded file for now. It's not needed after being loaded into docker.
- name: Remove downloaded image
env:
FILE: "${{ steps.downloadbackendimage.outputs.download_path }}"
run: rm $FILE
- name: tests
run: |
make codecov_prepare
Expand All @@ -165,6 +181,13 @@ jobs:
if: always()
with:
files: cover_db/codecov.json
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./tests/unit/outputs/junit.xml,./tests/integration/outputs/junit.xml

tests_dev:
name: 🧪 Test make dev
Expand All @@ -182,9 +205,17 @@ jobs:
key: taxonomies-${{ hashFiles('taxonomies/**') }}
restore-keys: taxonomies-
- name: Download backend image from artifacts
id: downloadbackendimage
uses: ishworkh/[email protected]
with:
image: "openfoodfacts-server/backend:dev"
# downloadbackendimage task loads the image into docker and keeps the original file.
# As our runs tend to hit the storage limits for GitHub Actions, manually delete the
# downloaded file for now. It's not needed after being loaded into docker.
- name: Remove downloaded image
env:
FILE: "${{ steps.downloadbackendimage.outputs.download_path }}"
run: rm $FILE
- name: set right UID and GID in .envrc
run: |
rm -f .envrc
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: release
uses: google-github-actions/[email protected].1
uses: googleapis/[email protected].3
with:
# We can't use GITHUB_TOKEN here because, github actions can't trigger actions
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow
Expand Down
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
# Changelog

## [2.52.0](https://github.com/openfoodfacts/openfoodfacts-server/compare/v2.51.0...v2.52.0) (2024-12-18)


### Features

* change redis stream name to product_updates ([#11141](https://github.com/openfoodfacts/openfoodfacts-server/issues/11141)) ([beb2b64](https://github.com/openfoodfacts/openfoodfacts-server/commit/beb2b64ad1e79446390c1caa890ddf729a13c444))
* launch new Nutri-Score for France ([#11123](https://github.com/openfoodfacts/openfoodfacts-server/issues/11123)) ([e1a7800](https://github.com/openfoodfacts/openfoodfacts-server/commit/e1a7800bf4e74abd9d31ae7637ec929426a92237))
* rename ecoscore fields to environmental_score (internal) and new external name and logos ([#11142](https://github.com/openfoodfacts/openfoodfacts-server/issues/11142)) ([8a1fec4](https://github.com/openfoodfacts/openfoodfacts-server/commit/8a1fec4718e78212e83ff8eb358c41cccd67cbd4))


### Bug Fixes

* nutripatrol URL ([#11115](https://github.com/openfoodfacts/openfoodfacts-server/issues/11115)) ([8c1d123](https://github.com/openfoodfacts/openfoodfacts-server/commit/8c1d123665734731a4353bfbebb33fbe4c77bed4))
* nutriscore messages ([#11140](https://github.com/openfoodfacts/openfoodfacts-server/issues/11140)) ([7c0ff96](https://github.com/openfoodfacts/openfoodfacts-server/commit/7c0ff96627b2e3acc7022ba2a03b5beef912c6d6))
* promo images for new nutri-score ([#11144](https://github.com/openfoodfacts/openfoodfacts-server/issues/11144)) ([8a37c8b](https://github.com/openfoodfacts/openfoodfacts-server/commit/8a37c8b0fa31003fbfa1cd32ccc56f40b0cf6f96))
* redirect /nutriscore-v2 to /new-nutriscore ([#11135](https://github.com/openfoodfacts/openfoodfacts-server/issues/11135)) ([3907e7c](https://github.com/openfoodfacts/openfoodfacts-server/commit/3907e7c4e823a4936b3abb1854bdfd715e1275d6))
* switch main nutriscore version to 2023 ([#11134](https://github.com/openfoodfacts/openfoodfacts-server/issues/11134)) ([6a0400f](https://github.com/openfoodfacts/openfoodfacts-server/commit/6a0400fd6dccc09150aefe87d39ca5d29beb589b))

## [2.51.0](https://github.com/openfoodfacts/openfoodfacts-server/compare/v2.50.0...v2.51.0) (2024-12-10)


### Features

* Add script to remove nearly empty products with quality issues ([#11058](https://github.com/openfoodfacts/openfoodfacts-server/issues/11058)) ([82726d5](https://github.com/openfoodfacts/openfoodfacts-server/commit/82726d5e082c01b13a105a46b0766c806f4ca13f))
* NOVA 4 attribute and knowledge panel improvements ([#11035](https://github.com/openfoodfacts/openfoodfacts-server/issues/11035)) ([9048011](https://github.com/openfoodfacts/openfoodfacts-server/commit/904801101b417773cd8d4fa574648741fb16f746))


### Bug Fixes

* additives table + clean HTML to remove some validation errors ([#11093](https://github.com/openfoodfacts/openfoodfacts-server/issues/11093)) ([474f68d](https://github.com/openfoodfacts/openfoodfacts-server/commit/474f68df98ebfe0895d62969882521563f18288e))
* avoid crash if ingredients services called without ingredients_lc ([#11055](https://github.com/openfoodfacts/openfoodfacts-server/issues/11055)) ([1db3e94](https://github.com/openfoodfacts/openfoodfacts-server/commit/1db3e94a85967973d286958504c7026668572f70))
* data quality, false positive, nutrition sum with lower symbol ([#11076](https://github.com/openfoodfacts/openfoodfacts-server/issues/11076)) ([d389c87](https://github.com/openfoodfacts/openfoodfacts-server/commit/d389c870e2cb09d696e836d3149ef6a996e7671f))
* data quality, false positive, nutrition sum with lower symbol for milk below the table ([#11098](https://github.com/openfoodfacts/openfoodfacts-server/issues/11098)) ([7febb69](https://github.com/openfoodfacts/openfoodfacts-server/commit/7febb698d2fe5d96e1ed9102f142a8107bfdd718))
* display of usage in scripts/import_csv_file.pl ([#11091](https://github.com/openfoodfacts/openfoodfacts-server/issues/11091)) ([91881f8](https://github.com/openfoodfacts/openfoodfacts-server/commit/91881f8ef7d8d663445068387cd449a5f291c3ea))
* improve parsing of 'category (type 1, type 2..)' ingredients ([#10999](https://github.com/openfoodfacts/openfoodfacts-server/issues/10999)) ([42618ac](https://github.com/openfoodfacts/openfoodfacts-server/commit/42618ac95cee57bfb901ccc1a12b8c1824a335ef))
* letter A at end of string is not a stopword ([#11095](https://github.com/openfoodfacts/openfoodfacts-server/issues/11095)) ([6eaeb26](https://github.com/openfoodfacts/openfoodfacts-server/commit/6eaeb26c028ce130c6159f5214858d125d3fe7ea))
* Load products in mongodb ([#11072](https://github.com/openfoodfacts/openfoodfacts-server/issues/11072)) ([6787ba1](https://github.com/openfoodfacts/openfoodfacts-server/commit/6787ba1b59a219b4f60edd32bceabfbd36c66047))
* new images path ([#11096](https://github.com/openfoodfacts/openfoodfacts-server/issues/11096)) ([8658959](https://github.com/openfoodfacts/openfoodfacts-server/commit/8658959a99417e03d6e7ceb1042fc4090e37cdd3))
* pro platform product writes to the public platform MongoDB database ([#11065](https://github.com/openfoodfacts/openfoodfacts-server/issues/11065)) ([f77eb82](https://github.com/openfoodfacts/openfoodfacts-server/commit/f77eb8248504d17bbc1e1cb5130a4bdbccc931d3))
* product image move [#11067](https://github.com/openfoodfacts/openfoodfacts-server/issues/11067) ([#11092](https://github.com/openfoodfacts/openfoodfacts-server/issues/11092)) ([30257c1](https://github.com/openfoodfacts/openfoodfacts-server/commit/30257c136e32a90bc84635f40b48b597eb860d66))
* remove warning in ecobalyse matching of ingredients ([#11062](https://github.com/openfoodfacts/openfoodfacts-server/issues/11062)) ([c29fce9](https://github.com/openfoodfacts/openfoodfacts-server/commit/c29fce9664167572574ce5ab3da75b146575c746))

## [2.50.0](https://github.com/openfoodfacts/openfoodfacts-server/compare/v2.49.0...v2.50.0) (2024-11-26)


Expand Down
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ ARG CPANMOPTS=
FROM debian:bullseye AS modperl

# Install cpm to install cpanfile dependencies
RUN --mount=type=cache,id=apt-cache,target=/var/cache/apt set -x && \
RUN --mount=type=cache,id=apt-cache,target=/var/cache/apt \
--mount=type=cache,id=lib-apt-cache,target=/var/lib/apt set -x && \
apt update && \
apt install -y \
apache2 \
Expand Down Expand Up @@ -80,7 +81,8 @@ RUN --mount=type=cache,id=apt-cache,target=/var/cache/apt set -x && \
# NB: not available in ubuntu 1804 LTS:
libgeoip2-perl \
libemail-valid-perl
RUN --mount=type=cache,id=apt-cache,target=/var/cache/apt set -x && \
RUN --mount=type=cache,id=apt-cache,target=/var/cache/apt \
--mount=type=cache,id=lib-apt-cache,target=/var/lib/apt set -x && \
apt install -y \
#
# cpan dependencies that can be satisfied by apt even if the package itself can't:
Expand Down
3 changes: 1 addition & 2 deletions Dockerfile.frontend
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ COPY --chown=node:node package*.json /opt/product-opener/
COPY --chown=node:node .snyk /opt/product-opener/
WORKDIR /opt/product-opener
USER node
RUN --mount=type=cache,id=npm-cache,target=/root/.npm npm install
RUN --mount=type=cache,id=npm-cacache,target=/root/.npm/_cacache npm install
ENV PATH /opt/product-opener/node_modules/.bin:$PATH

COPY --chown=node:node html /opt/product-opener/html
Expand All @@ -38,4 +38,3 @@ ARG USER_GID
RUN usermod -u $USER_UID www-data && \
groupmod --gid $USER_GID www-data
COPY --from=builder /opt/product-opener/html/ /opt/product-opener/html/
COPY --from=builder /opt/product-opener/node_modules/ /opt/product-opener/node_modules/
14 changes: 7 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ SHELL := $(shell which bash)
ENV_FILE ?= .env
NAME = "ProductOpener"
MOUNT_POINT ?= /mnt
# in CI, in make dev we want to skip downloading sample images (too slow)
SKIP_SAMPLE_IMAGES ?= SKIP_SAMPLE_IMAGES
DOCKER_LOCAL_DATA_DEFAULT = /srv/off/docker_data
DOCKER_LOCAL_DATA ?= $(DOCKER_LOCAL_DATA_DEFAULT)
OS := $(shell uname)
Expand Down Expand Up @@ -226,15 +224,15 @@ refresh_product_tags: run_deps
@echo "🥫 Refreshing product data cached in Postgres …"
${DOCKER_COMPOSE} run --rm backend perl /opt/product-opener/scripts/refresh_postgres.pl ${from}

import_sample_data:run_deps
import_sample_data: run_deps
@ if [[ "${PRODUCT_OPENER_FLAVOR_SHORT}" = "off" && "${PRODUCERS_PLATFORM}" != "1" ]]; then \
echo "🥫 Importing sample data (~200 products) into MongoDB …"; \
${DOCKER_COMPOSE} run --rm backend bash /opt/product-opener/scripts/import_sample_data.sh; \
${DOCKER_COMPOSE} run --rm -e SKIP_SAMPLE_IMAGES backend bash /opt/product-opener/scripts/import_sample_data.sh; \
else \
echo "🥫 Not importing sample data into MongoDB (only for po_off project)"; \
fi

import_more_sample_data:run_deps
import_more_sample_data: run_deps
@echo "🥫 Importing sample data (~2000 products) into MongoDB …"
${DOCKER_COMPOSE} run --rm backend bash /opt/product-opener/scripts/import_more_sample_data.sh

Expand Down Expand Up @@ -269,19 +267,21 @@ tests: build_taxonomies_test build_lang_test unit_test integration_test
# add COVER_OPTS='-e HARNESS_PERL_SWITCHES="-MDevel::Cover"' if you want to trigger code coverage report generation
unit_test: create_folders
@echo "🥫 Running unit tests …"
mkdir -p tests/unit/outputs/
${DOCKER_COMPOSE_TEST} up -d memcached postgres mongodb
${DOCKER_COMPOSE_TEST} run ${COVER_OPTS} -e PO_EAGER_LOAD_DATA=1 -T --rm backend yath test --job-count=${CPU_COUNT} -PProductOpener::LoadData tests/unit
${DOCKER_COMPOSE_TEST} run ${COVER_OPTS} -e JUNIT_TEST_FILE="tests/unit/outputs/junit.xml" -e PO_EAGER_LOAD_DATA=1 -T --rm backend yath test --renderer=Formatter --renderer=JUnit --job-count=${CPU_COUNT} -PProductOpener::LoadData tests/unit
${DOCKER_COMPOSE_TEST} stop
@echo "🥫 unit tests success"

integration_test: create_folders
@echo "🥫 Running integration tests …"
mkdir -p tests/integration/outputs/
# we launch the server and run tests within same container
# we also need dynamicfront for some assets to exists
# this is the place where variables are important
${DOCKER_COMPOSE_INT_TEST} up -d memcached postgres mongodb backend dynamicfront incron minion redis
# note: we need the -T option for ci (non tty environment)
${DOCKER_COMPOSE_INT_TEST} exec ${COVER_OPTS} -e PO_EAGER_LOAD_DATA=1 -T backend yath -PProductOpener::LoadData tests/integration
${DOCKER_COMPOSE_INT_TEST} exec ${COVER_OPTS} -e JUNIT_TEST_FILE="tests/integration/outputs/junit.xml" -e PO_EAGER_LOAD_DATA=1 -T backend yath --renderer=Formatter --renderer=JUnit -PProductOpener::LoadData tests/integration
${DOCKER_COMPOSE_INT_TEST} stop
@echo "🥫 integration tests success"

Expand Down
13 changes: 1 addition & 12 deletions cgi/product_image_move.pl
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@
my $code = normalize_code(single_param('code'));
my $imgids = single_param('imgids');
my $move_to = single_param('move_to_override');
if ($move_to =~ /^(off|obf|opf|opff)$/) {
$move_to .= ':' . $code;
}
elsif ($move_to ne 'trash') {
if ($move_to ne 'trash') {
$move_to = normalize_code($move_to);
}
my $copy_data = single_param('copy_data_override');
Expand Down Expand Up @@ -223,14 +220,6 @@

$response{url} = product_url($move_to);

# URL on another server?
my $server = server_for_product_id($move_to);
if (defined $server) {
my $url = "https://" . $subdomain . "." . $options{other_servers}{$server}{domain} . $response{url};
$url =~ s/\/([a-z]+):([0-9])/\/$2/;
$response{url} = $url;
}

$response{link} = '<a href="' . $response{url} . '">' . $move_to . '</a>';
}

Expand Down
8 changes: 4 additions & 4 deletions cgi/product_jqm_multilingual.pl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ =head1 DESCRIPTION
use ProductOpener::Ingredients qw/:all/;
use ProductOpener::Images qw/:all/;
use ProductOpener::DataQuality qw/:all/;
use ProductOpener::Ecoscore qw/:all/;
use ProductOpener::EnvironmentalScore qw/:all/;
use ProductOpener::Packaging qw/:all/;
use ProductOpener::ForestFootprint qw/:all/;
use ProductOpener::Text qw/remove_tags_and_quote/;
Expand Down Expand Up @@ -266,8 +266,8 @@ =head1 DESCRIPTION
push @app_fields, "creator";
}

if ($request_ref->{admin} or ($User_id eq "ecoscore-impact-estimator")) {
push @app_fields, ("ecoscore_extended_data", "ecoscore_extended_data_version");
if ($request_ref->{admin} or ($User_id eq "environmental-score-impact-estimator")) {
push @app_fields, ("environmental_score_extended_data", "environmental_score_extended_data_version");
}

# generate a list of potential languages for language specific fields
Expand Down Expand Up @@ -385,7 +385,7 @@ =head1 DESCRIPTION
}

}
elsif ($field eq "ecoscore_extended_data") {
elsif ($field eq "environmental_score_extended_data") {
# we expect a JSON value
if (defined single_param($field)) {
$product_ref->{$field} = decode_json(single_param($field));
Expand Down
Loading

0 comments on commit 4aab18e

Please sign in to comment.