Merge branch 'master' into PHRAS-2741-worker-service-part1

This commit is contained in:
Nicolas Maillat
2020-03-16 15:42:48 +01:00
committed by GitHub
63 changed files with 11705 additions and 1250 deletions

View File

@@ -1,12 +1,35 @@
.circleci
.git
.idea
.settings
nodes_modules
vendor
/*.md
/.circleci
/.dockerignore
/.env
/.env.*
/env.*
/.git
/.travis.yml
/AUTHORS
/CONTRIBUTORS
/Dockerfile
/LICENSE
/bin
!/bin/console
!/bin/developer
!/bin/setup
/cache
/config/configuration.yml
/config/configuration-compiled.php
!/config/configuration.sample.yml
/datas
/docker-compose.*
/logs
/nodes_modules
/plugins
/tmp
/vendor
/volumes
/www/assets
/www/custom
/www/plugins
/www/thumbnails
npm-debug.log

72
.env Normal file
View File

@@ -0,0 +1,72 @@
# Registry from where you pull Docker images
PHRASEANET_DOCKER_REGISTRY=local
# Tag of the Docker images
PHRASEANET_DOCKER_TAG=latest
# APPLICATION PORT
PHRASEANET_APP_PORT=8082
# RabbitMQ configuration
RABBITMQ_DEFAULT_USER=alchemy
RABBITMQ_DEFAULT_PASS=vdh4dpe5Wy3R
RABBITMQ_MANAGEMENT_PORT=10811
# Mysql configuration
MYSQL_ROOT_PASSWORD=root
SERVER_NAME=phraseanet-docker
# --------------- APPLICATION CONFIGURATION --------------------
# Max upload size
MAX_BODY_SIZE=2G
# Max input var
MAX_INPUT_VARS=12000
# Enable opcache ? (0/1)
OPCACHE_ENABLED=1
# session cache limiter (off/on)
SESSION_CACHE_LIMITER=off
# PHP LOG LEVEL : Possible Values: alert, error, warning, notice, debug
PHP_LOG_LEVEL=warning
# --------------- APPLICATION STARTUP CONFIGURATION --------------------
# These variables are only used if the configuration.yml file is not present, in order to automate the installation procedure
# set here the first user / email couple
INSTALL_ACCOUNT_EMAIL=admin@alchemy.fr
INSTALL_ACCOUNT_PASSWORD=iJRqXU0MwbyJewQLBbra6IWHsWly
# Database parameters
INSTALL_DB_HOST=db
INSTALL_DB_PORT=3306
INSTALL_DB_USER=root
INSTALL_DB_PASSWORD=root
INSTALL_DB_TEMPLATE=en-simple
INSTALL_APPBOX=ab_master
INSTALL_DATABOX=db_databox1
INSTALL_SERVER_NAME=localhost
# Mysql max allowed packet
MYSQL_MAX_ALLOWED_PACKET=16M
# --- DEV purpose ---
# PhpMyAdmin port
PHRASEANET_PHPMYADMIN_PORT=8089
# Xdebug
XDEBUG_ENABLED=1
XDEBUG_PROFILER_ENABLED=0
IDE_KEY=PHPSTORM
PHRASEANET_SUBNET_IPS=172.32.0.0/16
XDEBUG_REMOTE_HOST=172.32.0.1
PHP_IDE_CONFIG=serverName=docker-server-phraseanet
# Volumes location for dev
PHRASEANET_CONFIG_DIR=./config
PHRASEANET_LOGS_DIR=./logs
PHRASEANET_DATA_DIR=./datas
PHRASEANET_DB_DIR=./volumes/db
PHRASEANET_ELASTICSEARCH_DIR=./volumes/elasticsearch
PHRASEANET_THUMBNAILS_DIR=./www/thumbnails
PHRASEANET_TMP_DIR=./tmp

4
.gitignore vendored
View File

@@ -71,3 +71,7 @@ playbook.retry
npm-debug.log
/Phrasea_datas
.env.*
env.local
/volumes

View File

@@ -1,4 +1,3 @@
#########################################################################
# This image contains every build tools that will be used by the builder and
# the app images (usefull in dev mode)
@@ -11,7 +10,8 @@ RUN apt-get update \
ca-certificates \
gnupg2 \
&& apt-get update \
&& apt-get install -y --no-install-recommends zlib1g-dev \
&& apt-get install -y --no-install-recommends \
zlib1g-dev \
git \
ghostscript \
gpac \
@@ -34,20 +34,40 @@ RUN apt-get update \
unoconv \
unzip \
xpdf \
libreoffice-base-core \
libreoffice-impress \
libreoffice-calc \
libreoffice-math \
libreoffice-writer \
libreoffice-pdfimport \
&& update-locale "LANG=fr_FR.UTF-8 UTF-8" \
&& dpkg-reconfigure --frontend noninteractive locales \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-source extract \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-install zip exif iconv mbstring pcntl sockets xsl intl pdo_mysql gettext bcmath mcrypt \
&& pecl install redis amqp-1.9.3 zmq-beta imagick-beta \
&& pecl install \
redis \
amqp-1.9.3 \
zmq-beta \
imagick-beta \
xdebug-2.6.1 \
&& docker-php-ext-enable redis amqp zmq imagick \
&& pecl clear-cache \
&& docker-php-source delete \
&& rm -rf /var/lib/apt/lists/*
&& apt-get clean \
&& rm -rf /var/lib/apt/lists \
&& mkdir /entrypoint /var/alchemy \
&& useradd -u 1000 app \
&& mkdir -p /home/app/.composer \
&& chown -R app: /home/app /var/alchemy
ENV XDEBUG_ENABLED=0
#########################################################################
# This image is used to build the apps
#########################################################################
FROM phraseanet-system as builder
COPY --from=composer:1.9.1 /usr/bin/composer /usr/bin/composer
@@ -56,73 +76,33 @@ COPY --from=composer:1.9.1 /usr/bin/composer /usr/bin/composer
# https://linuxize.com/post/how-to-install-node-js-on-ubuntu-18.04/
# https://yarnpkg.com/lang/en/docs/install/#debian-stable
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \
&& apt install -y nodejs \
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update && apt-get install -y --no-install-recommends yarn \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
nodejs \
yarn \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/
&& rm -rf /var/lib/apt/lists \
&& mkdir -p /var/alchemy/Phraseanet \
&& chown -R app:app /var/alchemy
RUN mkdir /entrypoint /var/alchemy \
&& useradd -u 1000 app \
&& mkdir -p /home/app/.composer \
&& chown -R app: /home/app /var/alchemy
WORKDIR /var/alchemy/Phraseanet
#########################################################################
# This image is used to build the apps
#########################################################################
USER app
FROM phraseanet-system as builder
# Warm up composer cache for faster builds
COPY docker/caching/composer.* ./
RUN composer install --prefer-dist --no-dev --no-progress --no-suggest --classmap-authoritative --no-interaction --no-scripts \
&& rm -rf vendor composer.*
# End warm up
WORKDIR /var/alchemy/
COPY --chown=app . .
# Files that are needed at build stage
RUN rm -rf docker/phraseanet/root \
&& make install
COPY gulpfile.js /var/alchemy/
COPY www/include /var/alchemy/www/include
COPY www/scripts/apps /var/alchemy/www/scripts/apps
COPY Makefile /var/alchemy/
COPY package.json /var/alchemy/
COPY phpunit.xml.dist /var/alchemy/
COPY yarn.lock /var/alchemy/
COPY bin /var/alchemy/bin
COPY composer.json /var/alchemy/
COPY composer.lock /var/alchemy/
RUN make install_composer
COPY resources /var/alchemy/resources
# Application build phase
RUN make clean_assets
RUN make install_asset_dependencies
RUN make install_assets
# Application code
COPY www /var/alchemy/www
ADD ./docker/phraseanet/ /
COPY lib /var/alchemy/lib
COPY tmp /var/alchemy/tmp
COPY config /var/alchemy/config
COPY grammar /var/alchemy/grammar
COPY templates-profiler /var/alchemy/templates-profiler
COPY templates /var/alchemy/templates
COPY tests /var/alchemy/tests
# Create needed folders
RUN mkdir -p /var/alchemy/Phraseanet/logs \
&& chmod -R 777 /var/alchemy/Phraseanet/logs \
&& mkdir -p /var/alchemy/Phraseanet/cache \
&& chmod -R 777 /var/alchemy/Phraseanet/cache \
&& mkdir -p /var/alchemy/Phraseanet/datas \
&& chmod -R 777 /var/alchemy/Phraseanet/datas \
&& mkdir -p /var/alchemy/Phraseanet/tmp \
&& chmod -R 777 /var/alchemy/Phraseanet/tmp \
&& mkdir -p /var/alchemy/Phraseanet/www/custom \
&& chmod -R 777 /var/alchemy/Phraseanet/www/custom \
&& mkdir -p /var/alchemy/Phraseanet/config \
&& chmod -R 777 /var/alchemy/Phraseanet/config
ADD docker/phraseanet/ /
#########################################################################
# Phraseanet web application image
@@ -130,31 +110,29 @@ RUN mkdir -p /var/alchemy/Phraseanet/logs \
FROM phraseanet-system as phraseanet-fpm
RUN docker-php-source extract \
&& pecl install xdebug-2.9.0 \
&& docker-php-ext-enable xdebug \
#&& pecl clear-cache \
&& docker-php-source delete
COPY --from=builder --chown=app /var/alchemy /var/alchemy/Phraseanet
ADD ./docker/phraseanet/ /
COPY --from=builder --chown=app /var/alchemy/Phraseanet /var/alchemy/Phraseanet
ADD ./docker/phraseanet/root /
WORKDIR /var/alchemy/Phraseanet
ENTRYPOINT ["/phraseanet-entrypoint.sh"]
CMD ["/boot.sh"]
ENTRYPOINT ["docker/phraseanet/entrypoint.sh"]
CMD ["php-fpm", "-F"]
#########################################################################
# Phraseanet worker application image
#########################################################################
FROM phraseanet-fpm as phraseanet-worker
CMD ["/worker-boot.sh"]
ENTRYPOINT ["docker/phraseanet/worker/entrypoint.sh"]
CMD ["bin/console", "task-manager:scheduler:run"]
#########################################################################
# phraseanet-nginx
#########################################################################
FROM nginx:1.15 as phraseanet-nginx
RUN useradd -u 1000 app
ADD ./docker/nginx/ /
COPY --from=builder /var/alchemy/www /var/alchemy/Phraseanet/www
CMD ["/boot.sh"]
FROM nginx:1.17.8-alpine as phraseanet-nginx
RUN adduser --uid 1000 --disabled-password app
ADD ./docker/nginx/root /
COPY --from=builder /var/alchemy/Phraseanet/www /var/alchemy/Phraseanet/www
ENTRYPOINT ["/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -5,7 +5,10 @@ install:
make install_assets
install_composer:
composer install --ignore-platform-reqs
composer install --ignore-platform-reqs --no-dev --no-suggest --classmap-authoritative --no-interaction
install_composer_dev:
composer install
install_asset_dependencies:
yarn install

130
README.md
View File

@@ -32,7 +32,109 @@ You can also download a testing pre installed Virtual Machine in OVA format here
https://www.phraseanet.com/download/
# Development :
# With Docker
## Prerequisites
- docker-compose
- docker >=v18.01-ce
## Get started
You should review the default env variables defined in `.env` file.
Use `export` to override these values.
i.e:
```bash
export PHRASEANET_DOCKER_TAG=latest
export INSTALL_ACCOUNT_EMAIL=foo@bar.com
export INSTALL_ACCOUNT_PASSWORD=$3cr3t!
export PHRASEANET_APP_PORT=8082
```
### Using a env.local (custom .env)
It may be easier to deal with a local file to manage our env variables.
You can add your `env.local` at the root of this project and define a command alias in your `~/.bashrc`:
```bash
alias dc="env $(cat env.local | grep -v '#' | tr '\n' ' ') docker-compose"
```
### Running the application
If you are not interested in the development of Phraseanet, you can ignore everything in `.env` after the `DEV Purpose` part.
docker-compose -f docker-compose.yml up -d
Why this option `-f docker-compose.yml`?
The development and integration concerns are separated using a `docker-compose.override.yml`. By default, `docker-compose` will include this files if it exists.
If you don't work on phraseanet development, avoiding this `-f docker-compose.yml` parameters will throw errors. So you have to add this options on every `docker-compose` commands to avoid this inclusion.
> You can also delete the `docker-compose.override.yml` to get free from this behavior.
#### Running workers
```bash
docker-compose -f docker-compose.yml run --rm worker <command>
```
Where `<command>` can be:
- `bin/console task-manager:scheduler:run` (default)
- `bin/console worker:execute -m 2`
- ...
The default parameters allow you to reach the app with : `http://localhost:8082`
## Development mode
The development mode uses the `docker-compose-override.yml` file.
You can run it with:
docker-compose up -d
The environment is not ready yet: you have to fetch all dependencies.
This can be made easily from the builder container:
docker-compose run --rm -u app builder make install install_composer_dev
> Please note that the phraseanet image does not contain nor `composer` neither `node` tools. This allow the final image to be slim.
> If you need to use dev tools, ensure you are running the `builder` image!
### Using Xdebug
Xdebug is enabled by default with the `docker-compose.override.yml`
You can disable it by setting:
```bash
export XDEBUG_ENABLED=0
```
Remote host is fixed because of the subnet network from compose.
You need to configure file mapping in your IDE.
For PhpStorm, you can follow this example:
![PhpStorm mapping](https://i.ibb.co/GMb43Cv/image.png)
> Configure the `Absolute path on the server` to `/var/alchemy/Phraseanet` at the project root path (i.e. `~/projects/Phraseanet`).
#### Xdebug on MacOS
You have to set the following env:
```bash
XDEBUG_REMOTE_HOST=host.docker.internal
```
> Don't forget to recreate your container (`docker-compose up -d phraseanet`)
# With Vagrant (deprecated)
## Development :
For development purpose Phraseanet is shipped with ready to use development environments using vagrant.
You can easily choose betweeen a complete build or a prebuild box, with a specific PHP version.
@@ -50,29 +152,3 @@ Ex:
For development with Phraseanet API see https://docs.phraseanet.com/4.0/en/Devel/index.html
# Docker build
WARNING : still in a work-in-progress status and can be used only for test purposes.
The docker distribution come with 3 differents containers :
* An nginx that act as the front http server.
* The php-fpm who serves the php files through nginx.
* The worker who execute Phraseanet scheduler.
## How to build
You can build all the images with the following command at the root directory, choosing an arbirary TAG name :
./build.sh <TAG>
It will build and tag the following images :
local/phraseanet-worker:<TAG>
local/phraseanet-fpm:<TAG>
local/phraseanet-nginx:<TAG>
# Deploy the application
Once the images are built, you can deploy the entire phraseanet stack using the repository : https://github.com/alchemy-fr/phraseanet-docker and follow the instruction inside its `README.md` file.

View File

@@ -54,7 +54,7 @@ use Alchemy\Phrasea\Command\Task\TaskStart;
use Alchemy\Phrasea\Command\Task\TaskState;
use Alchemy\Phrasea\Command\Task\TaskStop;
use Alchemy\Phrasea\Command\User\UserCreateCommand;
use Alchemy\Phrasea\Command\User\UserSetPasswordCommand;
use Alchemy\Phrasea\Command\User\UserPasswordCommand;
use Alchemy\Phrasea\Command\User\UserListCommand;
use Alchemy\Phrasea\Command\UpgradeDBDatas;
@@ -132,7 +132,7 @@ $cli->command(new MountDataboxCommand('databox:mount'));
$cli->command(new UserCreateCommand('user:create'));
$cli->command(new UserSetPasswordCommand('user:set-password'));
$cli->command(new UserPasswordCommand('user:password'));
$cli->command(new UserListCommand('user:list'));

View File

@@ -1,13 +0,0 @@
#!/bin/bash
set -xe
# nginx server
docker build --target phraseanet-nginx -t local/phraseanet-nginx:$1 .
# php-fpm application
docker build --target phraseanet-fpm -t local/phraseanet-fpm:$1 .
# worker
docker build --target phraseanet-worker -t local/phraseanet-worker:$1 .

View File

@@ -0,0 +1,79 @@
version: "3.4"
services:
phpmyadmin:
image: phpmyadmin/phpmyadmin
restart: on-failure
ports:
- ${PHRASEANET_PHPMYADMIN_PORT}:80
depends_on:
- db
gateway:
volumes:
- .:/var/alchemy/Phraseanet
- ./docker/nginx/root/entrypoint.sh:/entrypoint.sh
- ${PHRASEANET_DATA_DIR}:/var/alchemy/Phraseanet/datas:rw
- ${PHRASEANET_THUMBNAILS_DIR}:/var/alchemy/Phraseanet/www/thumbnails:rw
- ${PHRASEANET_TMP_DIR}:/var/alchemy/Phraseanet/tmp:rw
builder:
build:
context: .
target: builder
command: exit 0
volumes:
- .:/var/alchemy/Phraseanet
- ${PHRASEANET_CONFIG_DIR}:/var/alchemy/Phraseanet/config:rw
- ${PHRASEANET_LOGS_DIR}:/var/alchemy/Phraseanet/logs:rw
- ${PHRASEANET_DATA_DIR}:/var/alchemy/Phraseanet/datas:rw
- ${PHRASEANET_THUMBNAILS_DIR}:/var/alchemy/Phraseanet/www/thumbnails:rw
phraseanet:
environment:
- XDEBUG_ENABLED
- XDEBUG_CONFIG=remote_host=${XDEBUG_REMOTE_HOST} idekey=${IDE_KEY} remote_enable=1 profiler_enable=${XDEBUG_PROFILER_ENABLED} profiler_output_dir=/var/alchemy/Phraseanet/cache/profiler
- PHP_IDE_CONFIG
volumes:
- .:/var/alchemy/Phraseanet
- ${PHRASEANET_CONFIG_DIR}:/var/alchemy/Phraseanet/config:rw
- ${PHRASEANET_LOGS_DIR}:/var/alchemy/Phraseanet/logs:rw
- ${PHRASEANET_DATA_DIR}:/var/alchemy/Phraseanet/datas:rw
- ${PHRASEANET_THUMBNAILS_DIR}:/var/alchemy/Phraseanet/www/thumbnails:rw
- ${PHRASEANET_TMP_DIR}:/var/alchemy/Phraseanet/tmp:rw
worker:
volumes:
- .:/var/alchemy/Phraseanet
- ${PHRASEANET_CONFIG_DIR}:/var/alchemy/Phraseanet/config:rw
- ${PHRASEANET_LOGS_DIR}:/var/alchemy/Phraseanet/logs:rw
- ${PHRASEANET_DATA_DIR}:/var/alchemy/Phraseanet/datas:rw
- ${PHRASEANET_THUMBNAILS_DIR}:/var/alchemy/Phraseanet/www/thumbnails:rw
- ${PHRASEANET_TMP_DIR}:/var/alchemy/Phraseanet/tmp:rw
rabbitmq:
ports:
- ${RABBITMQ_MANAGEMENT_PORT}:15672
db:
volumes:
- ${PHRASEANET_DB_DIR}:/var/lib/mysql:rw
mailhog:
image: mailhog/mailhog
ports:
- 1025:1025
- 8025:8025
elasticsearch:
ports:
- 9200:9200
volumes:
- ${PHRASEANET_ELASTICSEARCH_DIR}:/usr/share/elasticsearch/data:rw
networks:
default:
ipam:
config:
- subnet: $PHRASEANET_SUBNET_IPS

123
docker-compose.yml Normal file
View File

@@ -0,0 +1,123 @@
version: "3.4"
services:
gateway:
build:
context: .
target: phraseanet-nginx
image: $PHRASEANET_DOCKER_REGISTRY/phraseanet-nginx:$PHRASEANET_DOCKER_TAG
restart: on-failure
volumes:
- data_vol:/var/alchemy/Phraseanet/datas:rw
- thumbnails_vol:/var/alchemy/Phraseanet/www/thumbnails:rw
depends_on:
- phraseanet
environment:
- MAX_BODY_SIZE
ports:
- ${PHRASEANET_APP_PORT}:80
phraseanet:
build:
context: .
target: phraseanet-fpm
image: $PHRASEANET_DOCKER_REGISTRY/phraseanet-fpm:$PHRASEANET_DOCKER_TAG
restart: on-failure
depends_on:
- db
- redis
- rabbitmq
- elasticsearch
environment:
- MAX_BODY_SIZE
- MAX_INPUT_VARS
- OPCACHE_ENABLED
- SESSION_CACHE_LIMITER
- PHP_LOG_LEVEL
- INSTALL_ACCOUNT_EMAIL
- INSTALL_ACCOUNT_PASSWORD
- INSTALL_DB_HOST
- INSTALL_DB_PORT
- INSTALL_DB_USER
- INSTALL_DB_PASSWORD
- INSTALL_DB_TEMPLATE
- INSTALL_APPBOX
- INSTALL_DATABOX
- INSTALL_SERVER_NAME
- INSTALL_RABBITMQ_USER=$RABBITMQ_DEFAULT_USER
- INSTALL_RABBITMQ_PASSWORD=$RABBITMQ_DEFAULT_PASS
volumes:
- config_vol:/var/alchemy/Phraseanet/config:rw
- data_vol:/var/alchemy/Phraseanet/datas:rw
- tmp_vol:/var/alchemy/Phraseanet/tmp:rw
- logs_vol:/var/alchemy/Phraseanet/logs:rw
- thumbnails_vol:/var/alchemy/Phraseanet/www/thumbnails:rw
worker:
build:
context: .
target: phraseanet-worker
image: $PHRASEANET_DOCKER_REGISTRY/phraseanet-worker:$PHRASEANET_DOCKER_TAG
restart: on-failure
depends_on:
- db
- redis
- rabbitmq
- elasticsearch
environment:
- MAX_BODY_SIZE
- MAX_INPUT_VARS
- OPCACHE_ENABLED
- SESSION_CACHE_LIMITER
- PHP_LOG_LEVEL
volumes:
- config_vol:/var/alchemy/Phraseanet/config:rw
- data_vol:/var/alchemy/Phraseanet/datas:rw
- tmp_vol:/var/alchemy/Phraseanet/tmp:rw
- logs_vol:/var/alchemy/Phraseanet/logs:rw
- thumbnails_vol:/var/alchemy/Phraseanet/www/thumbnails:rw
db:
image: $PHRASEANET_DOCKER_REGISTRY/phraseanet-db:$PHRASEANET_DOCKER_TAG
build: ./docker/db
restart: on-failure
environment:
- MYSQL_ROOT_PASSWORD
- MYSQL_MAX_ALLOWED_PACKET
volumes:
- db_vol:/var/lib/mysql
rabbitmq:
image: rabbitmq:3-management
restart: on-failure
environment:
- RABBITMQ_DEFAULT_USER
- RABBITMQ_DEFAULT_PASS
redis:
image: redis
restart: on-failure
elasticsearch:
image: $PHRASEANET_DOCKER_REGISTRY/phraseanet-elasticsearch:$PHRASEANET_DOCKER_TAG
build: ./docker/elasticsearch
restart: on-failure
volumes:
- elasticsearch_vol:/usr/share/elasticsearch/data
volumes:
config_vol:
driver: local
data_vol:
driver: local
tmp_vol:
driver: local
db_vol:
driver: local
elasticsearch_vol:
driver: local
thumbnails_vol:
driver: local
# to be replacer by stdout/stderr
logs_vol:
driver: local

View File

@@ -0,0 +1,154 @@
{
"name": "phraseanet/phraseanet",
"description": "Phraseanet",
"license": "GPL-3.0",
"config": {
"bin-dir": "bin/"
},
"repositories": [
{
"type": "package",
"package": {
"name": "facebook/php-sdk",
"version": "3.2.3",
"source": {
"url": "https://github.com/facebookarchive/facebook-php-sdk.git",
"type": "git",
"reference": "3.2.3"
}
}
},
{
"type": "package",
"package": {
"name": "exiftool/exiftool",
"version": "11",
"source": {
"url": "https://github.com/exiftool/exiftool.git",
"type": "git",
"reference": "11.84"
}
}
},
{
"type": "vcs",
"url": "https://github.com/alchemy-fr/tcpdf-clone"
},
{
"type": "git",
"url": "https://github.com/romainneutron/ProcessManager.git"
},
{
"type": "vcs",
"url": "https://github.com/alchemy-fr/imagine"
},
{
"type": "vcs",
"url": "https://github.com/alchemy-fr/JMSTranslationBundle"
},
{
"type": "vcs",
"url": "https://github.com/alchemy-fr/embed-bundle.git"
},
{
"type": "git",
"url": "https://github.com/alchemy-fr/fractal.git"
}
],
"require": {
"php": ">=5.5.9",
"ext-intl": "*",
"alchemy-fr/tcpdf-clone": "~6.0",
"alchemy/embed-bundle": "^2.0.7",
"alchemy/geonames-api-consumer": "~0.1.0",
"alchemy/mediavorus": "^0.4.4",
"alchemy/oauth2php": "1.1.0",
"alchemy/phlickr": "0.2.9",
"alchemy/phpexiftool": "^0.7.0",
"alchemy/rest-bundle": "^0.0.5",
"alchemy/symfony-cors": "^0.1.0",
"alchemy/task-manager": "2.0.x-dev@dev",
"alchemy/zippy": "^0.3.0",
"beberlei/assert": "^2.3",
"cocur/slugify": "^2.0",
"dailymotion/sdk": "~1.5",
"data-uri/data-uri": "~0.1.0",
"dflydev/doctrine-orm-service-provider": "~1.0",
"doctrine/cache": "1.6.x-dev",
"doctrine/dbal": "^2.4.0",
"doctrine/migrations": "^1.0.0",
"doctrine/orm": "^2.4.0",
"elasticsearch/elasticsearch": "~2.0",
"firebase/php-jwt": "^3.0.0",
"gedmo/doctrine-extensions": "~2.3.0",
"goodby/csv": "^1.3.0",
"google/apiclient": "^2.0",
"guzzle/guzzle": "~3.0",
"hoa/compiler": "~2.0",
"hoa/console": "~2.0",
"hoa/dispatcher": "~0.0",
"hoa/router": "~2.0",
"igorw/get-in": "~1.0",
"imagine/imagine": "0.6.x-dev",
"jms/serializer": "~0.10",
"jms/translation-bundle": "dev-rebase-2015-10-20",
"justinrainbow/json-schema": "2.0.3 as 1.6.1",
"league/flysystem": "^1.0",
"league/flysystem-aws-s3-v2": "^1.0",
"league/fractal": "dev-webgalleries#af1acc0275438571bc8c1d08a05a4b5af92c9f97 as 0.13.0",
"media-alchemyst/media-alchemyst": "^0.5.5",
"monolog/monolog": "~1.3",
"mrclay/minify": "~2.1.6",
"neutron/process-manager": "2.0.x-dev@dev",
"neutron/recaptcha": "~0.1.0",
"neutron/silex-filesystem-provider": "~1.0",
"neutron/silex-imagine-provider": "~0.1.0",
"neutron/temporary-filesystem": "~2.1",
"pagerfanta/pagerfanta": "^1.0",
"php-ffmpeg/php-ffmpeg": "~0.5.0",
"php-xpdf/php-xpdf": "~0.2.1",
"exiftool/exiftool": "^11",
"ramsey/uuid": "^3.0",
"roave/security-advisories": "dev-master",
"silex/silex": "^1.3.0",
"silex/web-profiler": "~1.0",
"simple-bus/doctrine-orm-bridge": "^4.0",
"simple-bus/jms-serializer-bridge": "^1.0",
"simple-bus/message-bus": "^2.1",
"simple-bus/serialization": "^2.0",
"sorien/silex-dbal-profiler": "^1.1",
"sorien/silex-pimple-dumper": "^1.0",
"swiftmailer/swiftmailer": "~5.4.5",
"symfony/symfony": "~2.7.10|~2.8.3",
"themattharris/tmhoauth": "~0.7",
"twig/extensions": "^1.2.0",
"twig/twig": "~1.14, >=1.14.2",
"vierbergenlars/php-semver": "~2.1",
"webmozart/json": "^1.1",
"willdurand/negotiation": "^2.0.0-alpha1",
"zend/gdata": "~1.12.1",
"alchemy/worker-bundle": "^0.1.6",
"alchemy/queue-bundle": "^0.1.5",
"google/recaptcha": "^1.1",
"facebook/graph-sdk": "^5.6",
"box/spout": "^2.7",
"paragonie/random-lib": "^2.0",
"czproject/git-php": "^3.17"
},
"require-dev": {
"mikey179/vfsstream": "~1.5",
"phpunit/phpunit": "^4.8|^5.0"
},
"autoload": {
"psr-0": {
"Alchemy\\": "lib",
"": "lib/classes"
}
},
"include-path": ["vendor/zend/gdata/library"],
"extra": {
"branch-alias": {
"dev-master": "4.1.x-dev"
}
}
}

8839
docker/caching/composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
docker/db/Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM mariadb:10.4.5
RUN apt-get update && \
apt-get install -y \
gettext \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/
ADD ./docker/ /
ENTRYPOINT ["/phraseanet-entrypoint.sh"]
CMD ["mysqld","--sql_mode="]

View File

@@ -0,0 +1,3 @@
[mysqld]
max_allowed_packet=$MYSQL_MAX_ALLOWED_PACKET

View File

@@ -0,0 +1,5 @@
CREATE DATABASE ab_master CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE db_databox1 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE db_unitTest CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE db_dataset1 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE db_dataset2 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

View File

@@ -0,0 +1,7 @@
#!/bin/bash
set -eo pipefail
shopt -s nullglob
envsubst < /custom.cnf > /etc/mysql/conf.d/custom.cnf
docker-entrypoint.sh "$@"

View File

@@ -0,0 +1,3 @@
FROM elasticsearch:2.4
RUN /usr/share/elasticsearch/bin/plugin install analysis-icu

View File

@@ -1,6 +0,0 @@
#!/bin/bash
set -xe
cat nginx.conf.sample | sed "s/\$MAX_BODY_SIZE/$MAX_BODY_SIZE/g" > /etc/nginx/conf.d/default.conf
nginx -g "daemon off;"

View File

@@ -0,0 +1,7 @@
#!/bin/sh
set -xe
cat /nginx.conf.sample | sed "s/\$MAX_BODY_SIZE/$MAX_BODY_SIZE/g" > /etc/nginx/conf.d/default.conf
exec "$@"

View File

@@ -1,9 +0,0 @@
#!/bin/sh
set -e
if [ ! -z ${DOCKER_XDEBUG_ENABLED} ]; then
. usr-bin/docker-xdebug-enable
fi
exec "$@"

View File

@@ -1,4 +0,0 @@
#!/bin/sh
/sbin/ip route|awk '/default/ { print $3 }'
# TODO support MacOS & Windows host IP

View File

@@ -1,5 +0,0 @@
#!/bin/sh
unset HOST_IP
unset XDEBUG_CONFIG
unset XDEBUG_REMOTE_HOST

View File

@@ -1,9 +0,0 @@
#!/bin/sh
set -e
HOST_IP=$(docker-get-host-ip);
export HOST_IP
export XDEBUG_CONFIG="remote_enable=1 remote_host=${HOST_IP} idekey=PHPSTORM";
export XDEBUG_REMOTE_HOST="${HOST_IP}";

View File

@@ -2,12 +2,12 @@
set -xe
if [ $INSTALL_ACCOUNT_EMAIL = ""]; then
if [ -z "$INSTALL_ACCOUNT_EMAIL" ]; then
echo "INSTALL_ACCOUNT_EMAIL var is not set."
exit 1
fi
if [ $INSTALL_ACCOUNT_PASSWORD = ""]; then
if [ -z "$INSTALL_ACCOUNT_PASSWORD" ]; then
echo "INSTALL_ACCOUNT_PASSWORD var is not set."
exit 1
fi
@@ -35,11 +35,10 @@ fi
## Redis
/var/alchemy/Phraseanet/bin/setup system:config set main.cache.options.host redis
/var/alchemy/Phraseanet/bin/setup system:config set main.cache.options.port 6379
/var/alchemy/Phraseanet/bin/setup system:config set main.cache.options.domain $INSTALL_SERVER_NAME
/var/alchemy/Phraseanet/bin/setup system:config set main.cache.options.namespace $INSTALL_SERVER_NAME
/var/alchemy/Phraseanet/bin/setup system:config set main.cache.type redis
# RabbitMQ
bin/setup system:config set rabbitmq.server.host rabbitmq
bin/setup system:config set rabbitmq.server.port 5672
bin/setup system:config set rabbitmq.server.user $INSTALL_RABBITMQ_USER

View File

@@ -1,17 +0,0 @@
#!/bin/bash
set -xe
chown -R app:app /var/alchemy/Phraseanet/config
chown -R app:app /var/alchemy/Phraseanet/datas
chown -R app:app /var/alchemy/Phraseanet/tmp
chown -R app:app /var/alchemy/Phraseanet/www/thumbnails
FILE=/var/alchemy/Phraseanet/config/configuration.yml
if [ -f "$FILE" ]; then
echo "$FILE exist, skip setup."
else
echo "$FILE doesn't exist, entering setup..."
runuser app -c '/auto-install.sh'
fi
php-fpm -F

29
docker/phraseanet/entrypoint.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
set -e
envsubst < "docker/phraseanet/php.ini.sample" > /usr/local/etc/php/php.ini
envsubst < "docker/phraseanet/php-fpm.conf.sample" > /usr/local/etc/php-fpm.conf
chown -R app:app \
config \
datas \
tmp \
logs \
www/thumbnails
FILE=config/configuration.yml
if [ -f "$FILE" ]; then
echo "$FILE exists, skip setup."
else
echo "$FILE doesn't exist, entering setup..."
runuser app -c docker/phraseanet/auto-install.sh
fi
if [ ${XDEBUG_ENABLED} == "1" ]; then
echo "XDEBUG is enabled. YOU MAY KEEP THIS FEATURE DISABLED IN PRODUCTION."
docker-php-ext-enable xdebug
fi
bash -e docker-php-entrypoint $@

View File

@@ -1,19 +0,0 @@
#!/bin/bash
set -e
envsubst < /php.ini.sample > /usr/local/etc/php/php.ini
envsubst < /php-fpm.conf.sample > /usr/local/etc/php-fpm.conf
echo "XDEBUG=$XDEBUG"
if [ $XDEBUG = "ON" ]; then
echo "XDEBUG IS ENABLED. YOU MAY KEEP THIS FEATURE DISABLED IN PRODUCTION."
echo "xdebug.remote_enable=1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
echo "xdebug.remote_autostart=1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
echo "xdebug.remote_host=$XDEBUG_SERVER" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
echo "xdebug.remote_port=$XDEBUG_REMOTE_PORT" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
echo "xdebug.remote_handler=dbgp" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
echo "xdebug.remote_connect_back=0" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
#echo "xdebug.idekey=11896" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
fi
bash -e docker-php-entrypoint $@

View File

@@ -1,4 +0,0 @@
#!/bin/bash
mkdir /var/alchemy/Phraseanet/tmp/locks && chown -R app:app /var/alchemy/Phraseanet/tmp
runuser app -c 'php /var/alchemy/Phraseanet/bin/console task-manager:scheduler:run'

View File

@@ -0,0 +1,18 @@
#!/bin/bash
set -e
DOCKER_DIR="./docker/phraseanet"
mkdir -p "${APP_DIR}/tmp/locks" \
&& chown -R app:app "${APP_DIR}/tmp"
envsubst < "${DOCKER_DIR}/php.ini.sample" > /usr/local/etc/php/php.ini
envsubst < "${DOCKER_DIR}/php-fpm.conf.sample" > /usr/local/etc/php-fpm.conf
if [ ${XDEBUG_ENABLED} == "1" ]; then
echo "XDEBUG is enabled. YOU MAY KEEP THIS FEATURE DISABLED IN PRODUCTION."
docker-php-ext-enable xdebug
fi
runuser -u app "$@"

View File

@@ -246,6 +246,7 @@ class UserApplicationsCommand extends Command
$account->getUser()->getId(),
$application->getName(),
$application->getClientId(),
$application->getClientSecret(),
$application->getRedirectUri(),
($token) ? $token->getOauthToken() : '-',
$application->isPasswordGranted() ? "true": "false"
@@ -253,7 +254,7 @@ class UserApplicationsCommand extends Command
}
$applicationTable = $this->getHelperSet()->get('table');
$headers = ['app_id', 'user_id', 'name', 'client_id', 'callback_url', 'generated token', 'grant_password status'];
$headers = ['app_id', 'user_id', 'name', 'client_id', 'client_secret', 'callback_url', 'generated token', 'grant_password status'];
if ($jsonformat ) {
foreach ($applicationList as $appList) {
@@ -312,7 +313,7 @@ class UserApplicationsCommand extends Command
$application->isPasswordGranted() ? "true": "false"
];
$headers = ['client secret', 'client ID', 'Authorize endpoint url', 'Access endpoint', 'generated token', 'grant_password status'];
$headers = ['client_secret', 'client_id', 'Authorize endpoint url', 'Access endpoint', 'generated token', 'grant_password status'];
if ($jsonformat ) {
$createdAppInfo = array_combine($headers, $applicationCreated);
echo json_encode($createdAppInfo);

View File

@@ -30,8 +30,8 @@ class UserListCommand extends Command
{
parent::__construct('user:list');
$this->setDescription('List of all user (experimental)')
->addOption('user_id', null, InputOption::VALUE_OPTIONAL, ' The id of user export only info this user ')
$this->setDescription('List of all user <comment>(experimental)</>')
->addOption('user_id', null, InputOption::VALUE_OPTIONAL, 'The id of user export only info this user ')
->addOption('user_email', null, InputOption::VALUE_OPTIONAL, 'The mail of user export only info this user .')
->addOption('database_id', null, InputOption::VALUE_OPTIONAL, 'Id of database.')
->addOption('collection_id', null, InputOption::VALUE_OPTIONAL, 'Id of the collection.')
@@ -39,7 +39,6 @@ class UserListCommand extends Command
->addOption('guest', null, InputOption::VALUE_NONE, 'Only guest user')
->addOption('created', null, InputOption::VALUE_OPTIONAL, 'Created at with operator,aaaa-mm-jj hh:mm:ss.')
->addOption('updated', null, InputOption::VALUE_OPTIONAL, 'Update at with operator,aaaa-mm-jj hh:mm:ss.')
->addOption('application', null, InputOption::VALUE_NONE, 'List application of user work only if --user_id is set')
->addOption('right', null, InputOption::VALUE_NONE, 'Show right information')
->addOption('adress', null, InputOption::VALUE_NONE, 'Show adress information')
->addOption('models', null, InputOption::VALUE_NONE, "Show only defined models, if --user_id is set with --models it's the template owner")
@@ -58,7 +57,6 @@ class UserListCommand extends Command
$collectionId = $input->getOption('collection_id');
$lockStatus = $input->getOption('mail_lock_status');
$guest = $input->getOption('guest');
$application = $input->getOption('application');
$withAdress = $input->getOption('adress');
$created = $input->getOption('created');
$updated = $input->getOption('updated');
@@ -77,11 +75,6 @@ class UserListCommand extends Command
if($lockStatus && !$models) $query->addSqlFilter('Users.mail_locked = 1');
if($guest && !$models) $query->include_invite(true)->addSqlFilter('Users.guest = 1');
if ($application and !$userId) {
$output->writeln('<error>You must provide --user_id when using --application option</error>');
return 0;
}
/** @var UserRepository $userRepository */
$userRepository = $this->container['repo.users'];
@@ -94,11 +87,7 @@ class UserListCommand extends Command
}
$userList = [];
$showApplication = false;
foreach ($users as $key => $user) {
if ($userId and $application) {
$showApplication = true;
}
$userList[] = $this->listUser($user, $withAdress, $withRight);
$userListRaw[] = array_combine($this->headerTable($withAdress, $withRight), $this->listUser($user, $withAdress, $withRight));
@@ -113,15 +102,6 @@ class UserListCommand extends Command
->setRows($userList)
->render($output);
;
if ($showApplication) {
$applicationTable = $this->getHelperSet()->get('table');
$applicationTable->setHeaders(array(
array(new TableCell('Applications', array('colspan' => 5))),
['name','callback','client_secret','client_id','token'],
))->setRows($this->getApplicationOfUser($users[0]))->render($output);
}
}
@@ -215,37 +195,6 @@ class UserListCommand extends Command
];
}
/**
* @param User $user
* @return array
*/
private function getApplicationOfUser(User $user)
{
$apiRepository = $this->container['repo.api-applications'];
$applications = $apiRepository->findByUser($user);
if (empty($applications)) {
return [];
}
$accountRepository = $this->container['repo.api-accounts'];
$apiOauthRepository = $this->container['repo.api-oauth-tokens'];
$usersApplication = [];
foreach ($applications as $application) {
$account = $accountRepository->findByUserAndApplication($user, $application);
$token = $account ? $apiOauthRepository->findDeveloperToken($account) : null;
$usersApplication[] = [
$application->getName(),
$application->getRedirectUri(),
$application->getClientSecret(),
$application->getClientId(),
($token) ? $token->getOauthToken() : '-'
];
}
return $usersApplication;
}
/**
* @param $withAdress
* @param $withRight

View File

@@ -0,0 +1,178 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Command\User;
use Alchemy\Phrasea\Application\Helper\NotifierAware;
use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\Core\LazyLocator;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Notification\Receiver;
use Alchemy\Phrasea\Notification\Mail\MailRequestPasswordUpdate;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
class UserPasswordCommand extends Command
{
use NotifierAware;
/**
* Constructor
*/
public function __construct($name = null)
{
parent::__construct('user:password');
$this->setDescription('Set user password in Phraseanet <comment>(experimental)</>')
->addOption('user_id', null, InputOption::VALUE_REQUIRED, 'The id of user.')
->addOption('generate', null, InputOption::VALUE_NONE, 'Generate and set with a random value')
->addOption('password', null, InputOption::VALUE_OPTIONAL, 'Set the user password to the input value')
->addOption('send_renewal_email', null, InputOption::VALUE_NONE, 'Send email link to user for password renewing, work only if --password or --generate are not define')
->addOption('password_hash', null, InputOption::VALUE_OPTIONAL, 'Define a password hashed, work only with password_nonce')
->addOption('password_nonce', null, InputOption::VALUE_OPTIONAL, 'Define a password nonce, work only with password_hash')
->addOption('dump', null, InputOption::VALUE_NONE, 'Return the password hashed and nonce')
->addOption('jsonformat', null, InputOption::VALUE_NONE, 'Output in json format')
->addOption('yes', 'y', InputOption::VALUE_NONE, 'Answer yes to all questions')
->setHelp('');
return $this;
}
protected function doExecute(InputInterface $input, OutputInterface $output)
{
$dialog = $this->getHelperSet()->get('dialog');
$userRepository = $this->container['repo.users'];
$userManipulator = $this->container['manipulator.user'];
$user = $userRepository->find($input->getOption('user_id'));
$password = $input->getOption('password');
$generate = $input->getOption('generate');
$sendRenewalEmail = $input->getOption('send_renewal_email');
$dump = $input->getOption('dump');
$passwordHash = $input->getOption('password_hash');
$passwordNonce = $input->getOption('password_nonce');
$jsonformat = $input->getOption('jsonformat');
$yes = $input->getOption('yes');
if ($user === null) {
$output->writeln('<info>Not found User.</info>');
return 0;
}
if ($passwordHash && $passwordNonce) {
$user->setNonce($passwordNonce);
$user->setPassword($passwordHash);
$userManipulator->updateUser($user);
$output->writeln('<info>password set with hashed pass</info>');
return 0;
}
if ($dump) {
$oldHash = $user->getPassword();
$oldNonce = $user->getNonce();
}
if ($generate) {
$oldHash = $user->getPassword();
$oldNonce = $user->getNonce();
$password = $this->container['random.medium']->generateString(64);
} else {
if (!$password && $sendRenewalEmail) {
$this->sendPasswordSetupMail($user);
$output->writeln('<info>email link sended for password renewing!</info>');
return 0;
} elseif (!$password && !$sendRenewalEmail && ! $dump) {
$output->writeln('<error>choose one option to set a password!</error>');
return 0;
}
}
if ($password) {
if (!$yes) {
do {
$continue = mb_strtolower($dialog->ask($output, '<question>Do you want really set password to this user? (y/N)</question>', 'N'));
} while (!in_array($continue, ['y', 'n']));
if ($continue !== 'y') {
$output->writeln('Aborting !');
return;
}
}
$oldHash = $user->getPassword();
$oldNonce = $user->getNonce();
$userManipulator->setPassword($user,$password);
}
if ($dump) {
if ($jsonformat) {
$hash['password_hash'] = $oldHash;
$hash['nonce'] = $oldNonce;
echo json_encode($hash);
return 0;
} else {
$output->writeln('<info>password_hash :</info>' . $oldHash);
$output->writeln('<info>nonce :</info>' . $oldNonce);
return 0;
}
}
if (($password || $generate)) {
if ($jsonformat) {
$hash['new_password'] = $password;
$hash['previous_password_hash'] = $oldHash;
$hash['previous_nonce'] = $oldNonce;
echo json_encode($hash);
} else {
$output->writeln('<info>new_password :</info>' . $password);
$output->writeln('<info>previous_password_hash :</info>' . $oldHash);
$output->writeln('<info>previous_nonce :</info>' . $oldNonce);
}
}
return 0;
}
/**
* Send mail for renew password
* @param User $user
*/
private function sendPasswordSetupMail(User $user)
{
$this->setDelivererLocator(new LazyLocator($this->container, 'notification.deliverer'));
$receiver = Receiver::fromUser($user);
$token = $this->container['manipulator.token']->createResetPasswordToken($user);
$url = $this->container['url_generator']->generate('login_renew_password', [ 'token' => $token->getValue() ], true);
$mail = MailRequestPasswordUpdate::create($this->container, $receiver);
$servername = $this->container['conf']->get('servername');
$mail->setButtonUrl($url);
$mail->setLogin($user->getLogin());
$mail->setExpiration(new \DateTime('+1 day'));
$this->deliver($mail);
}
}

View File

@@ -1,79 +0,0 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Command\User;
use Alchemy\Phrasea\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
class UserSetPasswordCommand extends Command
{
/**
* Constructor
*/
public function __construct($name = null)
{
parent::__construct('user:set-password');
$this->setDescription('Set user password in Phraseanet')
->addOption('user_id', null, InputOption::VALUE_REQUIRED, 'The id of user.')
->addOption('generate', null, InputOption::VALUE_NONE, 'Generate the password')
->addOption('password', null, InputOption::VALUE_OPTIONAL, 'The password')
->setHelp('');
return $this;
}
protected function doExecute(InputInterface $input, OutputInterface $output)
{
$dialog = $this->getHelperSet()->get('dialog');
$userRepository = $this->container['repo.users'];
$userManipulator = $this->container['manipulator.user'];
$user = $userRepository->find($input->getOption('user_id'));
$password = $input->getOption('password');
$generate = $input->getOption('generate');
if ($user === null) {
$output->writeln('<info>Not found User.</info>');
return 0;
}
if ($generate) {
$password = $this->container['random.medium']->generateString(64);
} else {
if (!$password) {
$output->writeln('<error>--password option not specified</error>');
return 0;
}
}
do {
$continue = mb_strtolower($dialog->ask($output, '<question>Do you want really set password to this user? (y/N)</question>', 'N'));
} while (!in_array($continue, ['y', 'n']));
if ($continue !== 'y') {
$output->writeln('Aborting !');
return;
}
$userManipulator->setPassword($user,$password);
$output->writeln('New password: <info>' . $password . '</info>');
return 0;
}
}

View File

@@ -450,6 +450,51 @@ class LightboxController extends Controller
return $this->app->json($data);
}
/**
* @param Basket $basket
* @return Response
*/
public function ajaxGetElementsAction(Basket $basket)
{
$ret = [
'error' => false,
'datas' => [
'counts' => [
'yes' => 0,
'no' => 0,
'nul' => 0,
'total' => 0
]
]
];
try {
if (!$basket->getValidation()) {
throw new Exception('There is no validation session attached to this basket');
}
foreach ($basket->getElements() as $element) {
$vd = $element->getUserValidationDatas($this->getAuthenticatedUser());
if($vd->getAgreement() === true) {
$ret['datas']['counts']['yes']++;
}
elseif($vd->getAgreement() === false) {
$ret['datas']['counts']['no']++;
}
elseif($vd->getAgreement() === null) {
$ret['datas']['counts']['nul']++;
}
$ret['datas']['counts']['total']++;
}
}
catch (Exception $e) {
$ret = [
'error' => true,
'datas' => $e->getMessage()
];
}
return $this->app->json($ret);
}
/**
* @param Basket $basket
* @throws Exception

View File

@@ -105,6 +105,11 @@ class Lightbox implements ControllerProviderInterface, ServiceProviderInterface
->assert('basket', '\d+')
;
$controllers->get('/ajax/GET_ELEMENTS/{basket}/', 'controller.lightbox:ajaxGetElementsAction')
->bind('lightbox_ajax_get_elements')
->assert('basket', '\d+')
;
return $controllers;
}

View File

@@ -17,7 +17,7 @@ class Version
* @var string
*/
private $number = '4.1.0-alpha.23a';
private $number = '4.1.0-alpha.25a';
/**
* @var string

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2019 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
class patch_410alpha25a implements patchInterface
{
/** @var string */
private $release = '4.1.0-alpha.25a';
/** @var array */
private $concern = [base::APPLICATION_BOX];
/**
* Returns the release version.
*
* @return string
*/
public function get_release()
{
return $this->release;
}
/**
* {@inheritdoc}
*/
public function concern()
{
return $this->concern;
}
/**
* {@inheritdoc}
*/
public function require_all_upgrades()
{
return false;
}
/**
* {@inheritdoc}
*/
public function getDoctrineMigrations()
{
return [];
}
/**
* {@inheritdoc}
*/
public function apply(base $appbox, Application $app)
{
/** @var PropertyAccess $conf */
$conf = $app['conf'];
$oldOptions = $conf->get(['main', 'search-engine', 'options'], []);
$newOptions = [];
$facets = [];
// preserve former settings from conf (tech facets)
foreach($oldOptions as $k=>$v) {
if(substr($k, -16) === '_aggregate_limit') {
// this option is moved under "facets"
$k = substr($k, 0, strlen($k)-16); // keep field name
$facets['_'.$k] = ['limit' => $v];
}
else {
$newOptions[$k] = $v;
}
}
// add facets for fields
foreach($app->getDataboxes() as $databox) {
foreach($databox->get_meta_structure() as $field) {
$facets[$field->get_name()] = ['limit' => $field->getFacetValuesLimit()];
}
}
// facets in the end of settings
$newOptions['facets'] = $facets;
$conf->set(['main', 'search-engine', 'options'], $newOptions);
return true;
}
}

View File

@@ -90,13 +90,14 @@ class phraseadate
$compareTo = new DateTime('now');
$diff = $compareTo->format('U') - $date->format('U');
$yearDiff = $compareTo->format('Y') - $date->format('Y');
$dayDiff = floor($diff / 86400);
if (is_nan($dayDiff) || $dayDiff > 365000) {
return '';
}
$date_string = $this->formatDate($date, $this->app['locale'], 'DAY_MONTH');
$date_string = $this->formatDate($date, $this->app['locale'], ($yearDiff != 0) ? 'DAY_MONTH_YEAR' : 'DAY_MONTH');
if ($dayDiff == 0) {
if ($diff < 60) {

View File

@@ -1328,7 +1328,7 @@
<field>
<name>host</name>
<type>char(64)</type>
<type>char(255)</type>
<null></null>
<extra></extra>
@@ -1403,7 +1403,7 @@
</field>
<field>
<name>label_en</name>
<type>char(50)</type>
<type>char(128)</type>
<null>YES</null>
<extra></extra>
<default></default>
@@ -1411,7 +1411,7 @@
</field>
<field>
<name>label_fr</name>
<type>char(50)</type>
<type>char(128)</type>
<null>YES</null>
<extra></extra>
<default></default>
@@ -1419,7 +1419,7 @@
</field>
<field>
<name>label_de</name>
<type>char(50)</type>
<type>char(128)</type>
<null>YES</null>
<extra></extra>
<default></default>
@@ -1427,7 +1427,7 @@
</field>
<field>
<name>label_nl</name>
<type>char(50)</type>
<type>char(128)</type>
<null>YES</null>
<extra></extra>
<default></default>

View File

@@ -65,7 +65,7 @@
"normalize-css": "^2.1.0",
"npm": "^6.0.0",
"npm-modernizr": "^2.8.3",
"phraseanet-production-client": "0.34.142-d",
"phraseanet-production-client": "0.34.150-d",
"requirejs": "^2.3.5",
"tinymce": "^4.0.28",
"underscore": "^1.8.3",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2020-02-27T14:59:20Z" source-language="en" target-language="de" datatype="plaintext" original="not.available">
<file date="2020-03-13T14:06:24Z" source-language="en" target-language="de" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2020-02-27T15:00:00Z" source-language="en" target-language="en" datatype="plaintext" original="not.available">
<file date="2020-03-13T14:06:35Z" source-language="en" target-language="en" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2020-02-27T15:00:28Z" source-language="en" target-language="fr" datatype="plaintext" original="not.available">
<file date="2020-03-13T14:06:47Z" source-language="en" target-language="fr" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2020-02-27T15:00:57Z" source-language="en" target-language="nl" datatype="plaintext" original="not.available">
<file date="2020-03-13T14:07:03Z" source-language="en" target-language="nl" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -474,6 +474,17 @@ class Basket extends \Alchemy\Phrasea\Model\Entities\Basket implements \Doctrine
return parent::hasRecord($app, $record);
}
/**
* {@inheritDoc}
*/
public function getElementByRecord(\Alchemy\Phrasea\Application $app, \record_adapter $record)
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getElementByRecord', [$app, $record]);
return parent::getElementByRecord($app, $record);
}
/**
* {@inheritDoc}
*/

View File

@@ -518,4 +518,15 @@ class LazaretFile extends \Alchemy\Phrasea\Model\Entities\LazaretFile implements
return parent::getRecordsToSubstitute($app, $includeReason);
}
/**
* {@inheritDoc}
*/
public function getStatus(\Alchemy\Phrasea\Application $app)
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getStatus', [$app]);
return parent::getStatus($app);
}
}

View File

@@ -543,6 +543,23 @@ hr {
padding: 0 20px;
}
.modal-footer .btn-primary {
color: #fff!important;
background-color: #007bff!important;
border-color: #007bff!important;
}
.modal-header .close {
display: inline-block;
float: right;
width: 25px;
opacity: 1;
background: #e1e1e1;
}
.hide {
display: none;
}
button.confirm_report {
background: #38c !important;
color: #fff !important;

View File

@@ -13,5 +13,7 @@
</a>
{% endif %}
</div>
{% endif %}

View File

@@ -4,10 +4,12 @@
<script type="text/javascript" src="/assets/production/commons{% if not app.debug %}.min{% endif %}.js"></script>
<script type="text/javascript" src="/assets/production/lightbox-mobile{% if not app.debug %}.min{% endif %}.js"></script>
{# <script type="text/javascript" src="/assets/lightbox/js/lightbox-mobile{% if not app.debug %}.min{% endif %}.js"></script>#}
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
{% endblock %}
{% block stylesheet %}
<link type="text/css" rel="stylesheet" href="/assets/lightbox/css/lightbox-mobile{% if not app.debug %}.min{% endif %}.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
{% endblock %}
{% block icon %}
@@ -62,7 +64,6 @@
</div>
</div>
<div id="about" data-role="page">
<div data-role="header">
<a href="#home" data-rel="back" data-icon="arrow-l">{{ 'Back' | trans }}</a>
@@ -99,6 +100,7 @@
{{ 'Validations' | trans }}
<span>{{_self.valid_baskets_length(baskets_collection)}}</span>
</p>
<p class="lightbox-msg">
{{ 'Voici vos validations en cours' | trans }}
</p>
@@ -168,7 +170,6 @@
</div>
</div>
<script>
var lightboxApp = lightboxMobileApplication.bootstrap({
lang: '{{ app.locale }}',

View File

@@ -4,6 +4,7 @@
{% block javascript %}
<script type="text/javascript" src="/assets/production/commons{% if not app.debug %}.min{% endif %}.js"></script>
<script type="text/javascript" src="/assets/production/lightbox-mobile{% if not app.debug %}.min{% endif %}.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script type="text/javascript">
{% if basket.getValidation() %}
@@ -14,13 +15,15 @@
releasable: {% if basket.getValidation().getParticipant(app.getAuthenticatedUser()).isReleasable() %}"{{ 'Do you want to send your report ?' | trans }}"{% else %}false{% endif %}
});
//var releasable = {% if basket.getValidation().getParticipant(app.getAuthenticatedUser()).isReleasable() %}"{{ 'Do you want to send your report ?' | trans }}"{% else %}false{% endif %}
{% endif %}
</script>
{% endblock %}
{% block stylesheet %}
<link type="text/css" rel="stylesheet" href="/assets/lightbox/css/lightbox-mobile{% if not app.debug %}.min{% endif %}.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
{% endblock %}
{% block content %}
@@ -41,7 +44,40 @@
{% set basket_length = basket.getElements().count() %}
{% trans with {'%basket_length%' : basket_length} %}%basket_length% documents{% endtrans %}
</p>
{% if basket.getValidation() %}
<div id="FeedbackRelease" class="modal fade" role="dialog" style="display: none">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
&times;</button>
<h4 class="modal-title">{{ 'lightbox:feedback:sendreport:warnwindows:title' | trans }}</h4>
</div>
<div class="modal-body">
<p>{{'lightbox:feedback:sendreport:warnwindows:message ' | trans}}</p>
<p>{{ 'lightbox:feedback:sendreport:warnwindows:record_approved'| trans }} : <span class="record_accepted"></span>
</p>
<hr>
<p>
{{'lightbox:feedback:sendreport:warnwindows:record_rejected'| trans }} : <span class="record_refused"></span>
</p>
<hr>
<p>
{{'lightbox:feedback:sendreport:warnwindows:record_unexpressed'| trans }} : <span class="record_null"></span>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" style="width:auto">{{ 'lightbox:feedback:sendreport:warnwindows:cancel'| trans }}</button>
<button type="button" class="btn btn-primary" id="validate-release" style="width:auto">{{ 'lightbox:feedback:sendreport:warnwindows:validate'| trans }}</button>
</div>
</div>
</div>
</div>
<div class="report_wrapper">
<a id="report_summary" class="report_btn report_summary" href="#" onclick="$.ajax({
type: 'GET',
@@ -92,7 +128,11 @@
<div class="btn-container">
{% if basket.getValidation() and basket.getValidation().getParticipant(app.getAuthenticatedUser()).getCanAgree() %}
<button class="confirm_report" style="width:100%;" title="{{ 'validation::envoyer mon rapport' | trans }}">
<button class="confirm_report" style="width:100%;" title="{{ 'validation::envoyer mon rapport' | trans }}" onclick="
">
{{ 'validation::envoyer mon rapport' | trans }}
<img src="/assets/common/images/icons/loader1F1E1B.gif" style="visibility:hidden;" class="loader"/>
</button>
@@ -100,7 +140,6 @@
</div>
</div>
</div>
{% endblock %}

View File

@@ -116,6 +116,39 @@
{% endif %}
</div>
</div>
<div id="FeedbackRelease" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
&times;</button>
<h4 class="modal-title">{{ 'lightbox:feedback:sendreport:warnwindows:title' | trans }}</h4>
</div>
<div class="modal-body">
<p>{{'lightbox:feedback:sendreport:warnwindows:message ' | trans}}</p>
<p>{{ 'lightbox:feedback:sendreport:warnwindows:record_approved'| trans }} : <span class="record_accepted"></span>
</p>
<hr>
<p>
{{'lightbox:feedback:sendreport:warnwindows:record_rejected'| trans }} : <span class="record_refused"></span>
</p>
<hr>
<p>
{{'lightbox:feedback:sendreport:warnwindows:record_unexpressed'| trans }} : <span class="record_null"></span>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'lightbox:feedback:sendreport:warnwindows:cancel'| trans }}</button>
<button type="button" class="btn btn-primary" id="validate-release">{{ 'lightbox:feedback:sendreport:warnwindows:validate'| trans }}</button>
</div>
</div>
</div>
</div>
<div class="PNB" id="bottom_container">
<div class="PNB" style="height:30px;bottom:auto;">
<table border="0" cellspacing="0" cellpadding="0" style="width:100%;">

View File

@@ -8,14 +8,23 @@
{% block content %}
<div class="noToolTipResize" style="margin:5px;width:{{ width - 40 }}px;height:auto !important;height:380px;max-height:380px;min-height:220px;position:relative;">
<span class="expiredDate">
{% if basket.getValidation() %}
{% set dateExpired = app['date-formatter'].getPrettyString(basket.getValidation().getExpires()) %}
{{ 'workzone:feedback:expiration' | trans }} : {{ dateExpired }}
{% endif %}
</span>
<div style="margin:5px 0;max-height:160px;overflow:hidden;text-overflow:ellipsis;">
{{ basket.getDescription()|nl2br }}
</div>
<div style="margin:5px 0;text-align:right;font-style:italic;position:relative;">
<div style="margin:5px 0;font-style:italic;position:relative;width: 100%">
{% set nb_records = basket.getElements()|length %}
{% set date = app['date-formatter'].getPrettyString(basket.getUpdated()) %}
{% trans with {'%nb_records%' : nb_records} %}%nb_records% records{% endtrans %}
- {{ date }}
<div style="text-align:right">
{% trans with {'%nb_records%' : nb_records} %}%nb_records% records{% endtrans %}
- {{ 'prod:workzone:basket:creation-date' | trans }} : {{ date }}
</div>
<hr/>
<div style="position:relative;float:left;width:470px;">
{% for element in basket.getElements() %}

View File

@@ -1,3 +1,4 @@
<div>
<button class="tools-accordion">Actions</button>
<div class="tools-panel">
@@ -88,8 +89,30 @@
{% endfor %}
{% endif %}
</div>
{% if basket.getValidation() %}
<div class="feedback-info">
{% set nb_records = basket.getElements()|length %}
{% set date = app['date-formatter'].getPrettyString(basket.getUpdated()) %}
<p class="create-date">
{{ 'prod:workzone:basket:creation-date' | trans }} : {{ date }}
</p>
<div class="record-number"> {% trans with {'%nb_records%' : nb_records} %}%nb_records% records{% endtrans %}</div>
{% set dateExpired = app['date-formatter'].getPrettyString(basket.getValidation().getExpires()) %}
<div class="expiredDate">
<span>{{ 'workzone:feedback:expiration' | trans }} :</span>
<form class="update-feed-validation">
<input type="hidden" name="feedback-id" value={{ basket.getValidation().getId}} />
<input type="hidden" name="feedback-deadline-format" class="alternate" />
<input type="text" class="btn datepicker" name="feedback-deadline" value="{{ dateExpired }}">
<button type="submit" class="submit" title="{{ 'workzone:feedback:update' | trans }}"><i class="fa fa-save"></i></button>
</form>
</div>
{% endif %}
</div>
</div>
<div class="basket-content">
{% set basket_length = basket.getElements()|length %}
<div class="alert_datas_changed ui-corner-all">{{ 'Certaines donnees du panier ont change' | trans }} <a class="basket_refresher" href="#">{{ 'rafraichir' | trans }}</a></div>
{% if basket_length == 0 %}
@@ -102,10 +125,38 @@
{% if basket.getValidation() %}
{{ Macros.display_validation(app, basket, ordre) }}
{% else %}
{{ Macros.display_basket(app, basket) }}
{% endif %}
</div>
<script type="text/javascript">
$( ".update-feed-validation" ).on( "submit", function( event ) {
event.preventDefault();
console.log( $( this ).serialize() );
});
$( function() {
$.datepicker.regional['default'] = {
closeText: "{{ 'workzone:datepicker:closeText' | trans }}",
prevText: "{{ 'workzone:datepicker:prevText' | trans }}",
nextText: "{{ 'workzone:datepicker:nextText' | trans }}",
currentText: "{{ 'workzone:datepicker:currentText' | trans }}",
monthNames: [ "{{ 'workzone:datepicker:january' | trans }}", "{{ 'workzone:datepicker:february' | trans }}", "{{ 'workzone:datepicker:march' | trans }}", "{{ 'workzone:datepicker:april' | trans }}", "{{ 'workzone:datepicker:may' | trans }}", "{{ 'workzone:datepicker:june' | trans }}",
"{{ 'workzone:datepicker:july' | trans }}", "{{ 'workzone:datepicker:august' | trans }}", "{{ 'workzone:datepicker:september' | trans }}", "{{ 'workzone:datepicker:october' | trans }}", "{{ 'workzone:datepicker:november' | trans }}", "{{ 'workzone:datepicker:december' | trans }}" ],
dayNames: [ "{{ 'workzone:datepicker:sunday' | trans }}", "{{ 'workzone:datepicker:monday' | trans }}", "{{ 'workzone:datepicker:tuesday' | trans }}", "{{ 'workzone:datepicker:wednesday' | trans }}", "{{ 'workzone:datepicker:thursday' | trans }}", "{{ 'workzone:datepicker:friday' | trans }}", "{{ 'workzone:datepicker:saturday' | trans }}" ],
dayNamesMin: [ "D","L","M","M","J","V","S" ],
dateFormat: "d MM yy",
altField: ".alternate",
altFormat: "yy-mm-dd"
};
$( ".datepicker" ).datepicker( $.datepicker.regional[ 'default' ]);
})
</script>

View File

@@ -37,10 +37,11 @@
{% set content = WorkZone.getContent(srt) %}
<div id="validations-block" class="validations-block">
{% for basket in content.get(constant('\\Alchemy\\Phrasea\\Helper\\WorkZone::VALIDATIONS')) %}
<div tooltipsrc="{{ path('prod_tooltip_basket', { 'basket' : basket.getId() }) }}"
id="SSTT_{{basket.getId()}}"
class="basketTips ui-accordion-header ui-state-default
ui-corner-all header SSTT basket {% if not basket.isRead() %}unread{% endif %}
ui-corner-all header SSTT basket {% if not basket.getId() %}unread{% endif %}
{% if basket.getId() == selected_id and selected_type == 'basket' %}active{% endif %}">
<a class="workzone-menu-title" href="{{ path('prod_baskets_basket', { 'basket' : basket.getId() }) }}">
<span>
@@ -52,6 +53,7 @@
{{basket.getName()|e}}
</span>
</a>
<div class="menu">
<table>
<tr>
@@ -113,6 +115,7 @@
</div>
<div id="all_baskets-block" class="all_baskets-block">
{% for basket in content.get(constant('\\Alchemy\\Phrasea\\Helper\\WorkZone::BASKETS')) %}
<div tooltipsrc="{{ path('prod_tooltip_basket', { 'basket' : basket.getId() }) }}"
id="SSTT_{{basket.getId()}}"
class="basketTips ui-accordion-header ui-state-default

View File

@@ -7577,10 +7577,10 @@ phraseanet-common@^0.4.5-d:
js-cookie "^2.1.0"
pym.js "^1.3.1"
phraseanet-production-client@0.34.142-d:
version "0.34.142-d"
resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.142-d.tgz#9b66d9722e316c9af0ec35c9f497932b70c28c2e"
integrity sha512-U4gholdgWgjas16yerS+XqfrTcAhOCMMwXSbj8sapCdHUxy1GnSSelKcx/OTp+IPU/0oWpjT0I6M27azxhA/Sw==
phraseanet-production-client@0.34.150-d:
version "0.34.150-d"
resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.150-d.tgz#625ea96c045719b405fe9b707b632b1290aca285"
integrity sha512-JrtPq6dCTCBxX6kGViXZj4Sc26PMFIaifT3PVD1WHLUCNd/U3nnHzpzKUgVC0ibqOj4aVwA8JW/oMilGEe3cmg==
dependencies:
"@mapbox/mapbox-gl-language" "^0.9.2"
"@turf/turf" "^5.1.6"