Merge branch 'PHRAS-3078-Prod_redirect_end_session' of https://github.com/alchemy-fr/Phraseanet into PHRAS-3078-Prod_redirect_end_session

This commit is contained in:
Harrys Ravalomanana
2020-07-21 17:30:43 +04:00
104 changed files with 5665 additions and 2067 deletions

20
.env
View File

@@ -2,7 +2,7 @@ PHRASEANET_PROJECT_NAME=Phraseanet
# Registry from where you pull Docker images
PHRASEANET_DOCKER_REGISTRY=local
# Tag of the Docker images
PHRASEANET_DOCKER_TAG=latest
PHRASEANET_DOCKER_TAG=4.1.1
# APPLICATION PORT
PHRASEANET_APP_PORT=8082
# RabbitMQ configuration
@@ -13,6 +13,8 @@ RABBITMQ_MANAGEMENT_PORT=10811
MYSQL_ROOT_PASSWORD=root
SERVER_NAME=phraseanet-docker
# --------------- GATEWAY TIMEOUT -----------------------
GATEWAY_SEND_TIMEOUT=120
# --------------- PHP CONFIGURATION --------------------
@@ -21,6 +23,9 @@ MAX_BODY_SIZE=2G
# Max input var
MAX_INPUT_VARS=12000
MAX_EXECUTION_TIME=120
MAX_INPUT_TIME=60
# Enable opcache ? (0/1)
OPCACHE_ENABLED=1
# session cache limiter (off/on)
@@ -34,6 +39,8 @@ PHP_LOG_LEVEL=warning
# These variables are used in the configuration.yml .
# set here the first user / email couple
#set to id of Phraseanet root account, if you want activate a sync for Phraseanet root account password provide by PHRASEANET_ADMIN_ACCOUNT_PASSWORD env value.
PHRASEANET_ADMIN_ACCOUNT_ID=
PHRASEANET_ADMIN_ACCOUNT_EMAIL=admin@alchemy.fr
PHRASEANET_ADMIN_ACCOUNT_PASSWORD=iJRqXU0MwbyJewQLBbra6IWHsWly
# Database parameters
@@ -64,6 +71,16 @@ PHRASEANET_SMTP_SECURE_MODE=tls
PHRASEANET_SMTP_USER=
PHRASEANET_SMTP_PASSWORD=
# Locale setting
LC_MESSAGES=C.UTF-8
LC_COLLATE=C.UTF-8
LC_IDENTIFICATION=C.UTF-8
LANG=C.UTF-8
LC_MEASUREMENT=C.UTF-8
LC_CTYPE=C.UTF-8
LC_TIME=C.UTF-8
LC_NAME=C.UTF-8
# --- DEV purpose ---
@@ -86,6 +103,7 @@ PHRASEANET_DB_DIR=./volumes/db
PHRASEANET_ELASTICSEARCH_DIR=./volumes/elasticsearch
PHRASEANET_THUMBNAILS_DIR=./www/thumbnails
PHRASEANET_CUSTOM_DIR=./www/custom
PHRASEANET_PLUGINS_DIR=./www/plugins
PHRASEANET_TMP_DIR=./tmp
PHRASEANET_CACHE_DIR=./cache
PHRASEANET_DOWNLOAD_DIR=./datas/download

View File

@@ -1,5 +1,155 @@
# CHANGELOG
## 4.1.1
### Change summary
- Phraseanet now using Docker. Retrieve all official images on DockerHub
- Worker manager, a new way for all operations on assets. In the near future, this will replace the current task manager.
- Geolocation based on Mapbox (requires an account on Mapbox https://www.mapbox.com).
- Video chaptering and subtitling support.
- GUI redesign for Push, Feedback, List manager, Lightbox on mobile.
this version is finale version of 4.1.0 published in preview at start of year, a lot of improvement, bugfixes on several elements see summary here
### New Feature summary
* [PHRAS-2023] - Refacto Lightbox mobile in 4.1
* [PHRAS-2219] - Refacto design Push screen
* [PHRAS-2220] - Refacto design Feedback screen
* [PHRAS-2221] - Refacto design List manager general screen
* [PHRAS-2222] - Refacto design ListManager Advance Mode screen
* [PHRAS-2223] - Refacto dev list manager Advance Mode screen
* [PHRAS-2541] - Dev-Design-Prod/Publish Screen
* [PHRAS-2548] - Phraseanet Docker and Docker Compose
* [PHRAS-1226] - Geolocalisation In Phraseanet
* [PHRAS-1626] - bin/console databox:mount mount an existing databox
* [PHRAS-1628] - bin/console collection:publish
* [PHRAS-1630] - bin/console database:unmout
* [PHRAS-1631] - bin/console collection:unpublish
* [PHRAS-1648] - bin/console user:password
* [PHRAS-1659] - bin/console user:create
* [PHRAS-1771] - bin/console collection:unpublish
* [PHRAS-1773] - bin/console collection:publish
* [PHRAS-2518] - Phraseanet worker Read/Write metadata
* [PHRAS-2520] - Phraseanet worker send webhook
* [PHRAS-2738] - Phraseanet worker populate database
* [PHRAS-2435] - Phraseanet Worker Build subdefinition
* [PHRAS-2436] - Phraseanet Worker build zip export and send mail
* [PHRAS-2636] - Phraseanet Worker fetch assets from external uploader (pull mode)
* [PHRAS-2904] - Fullfill field define in geoloc - position field with information return by Geonames
* [PHRAS-161] - PROD Add a maps for geolocalisation of media in detailed view
* [PHRAS-1935] - View prod/ Video chapter editor
* [PHRAS-2997] - Matomo analytic service in Phraseanet
* [PHRAS-1890] - Add GS1 databases model to Phraseanet
### Improvement and fix summary
* [PHRAS-1561] - Prod | Print - Use the label of field when print, use the GUI user language
* [PHRAS-2067] - Prod : Introduce thumbnail & preview generic images for Fonts records
* [PHRAS-2473] - Populate Optimisation, sometime populate databox (database) is very long
* [PHRAS-2524] - Put worker log in ELK
* [PHRAS-2739] - incorporate Phraseanet-plugin-SubdefWebhook into Phraseanet
* [PHRAS-2157] - Prod / Share : Iframe sizes are set to 0 for audio documents
* [PHRAS-2538] - Some MP4 file is not correctly detected by Phraseanet.
* [PHRAS-2825] - Prod : Add a reset button to initialize searches filters
* [PHRAS-1872] - prod/export by email / subject are NOK
* [PHRAS-2342] - Report : collections not selected
* [PHRAS-2343] - report : all fields of all databases
* [PHRAS-2350] - Report : url is too long
* [PHRAS-2476] - Bad header in generated video preview file
* [PHRAS-2196] - API - Stories records pagination on search answer and Stories fetch info
* [PHRAS-2880] - extend admin GUI for define facets ordering.
* [PHRAS-2967] - Lightbox - dev of send email report - warn windows
* [PHRAS-1752] - update facebook sdk dependency
* [PHRAS-2678] - add `webhook monitor`
* [PHRAS-2915] - Lightbox (desktop version) Change sort order for basket and Feedback in landing page ( most recent in first)
* [PHRAS-2082] - Bump design of windows create user , create template user, create new subdef
* [PHRAS-2676] - Weaked download behaviour for large amount of data
* [PHRAS-2671] - Change behavior of preview display in audio file case
* [PHRAS-2879] - Define facets order in GUI and query result
## 4.0.12
Release notes - Phraseanet - Version 4.0.12
### Improvement
* [PHRAS-2955] - Cache doctrine entity metadata for performance
* [PHRAS-2964] - Application-box - set host colon of table sbas set to char 255
* [PHRAS-3012] - [PHRAS-2977] - Docker compose optimisation, refacto volumes, build image
optimisation, add Phraseanet plugin in build image, bump ffmpeg version in worker,
fix error un redis configuration.
more option for define volumes during installation process.
* [PHRAS-3027] - Backport To 4.0 - Populate - Slow query - due to LIMIT in sql query.
* [PHRAS-3027] - Translation improvement in EN and DE.
### Bugfix
* [PHRAS-2979] - The content of a story is not displayed even for users with appropriate on the collection
## 4.1.0
Pre release of 4.1
## 4.0.11
Release notes - Phraseanet - Version 4.0.11
### New Feature and Improvement
* [PHRAS-2878] - Print feedback report in PDF
* [PHRAS-2757] - Exclude some collections from quarantine checkers sha256, UUID, filename (AKA exclude Trash from quarantine)
* [PHRAS-2766] - Add status change capabilities to quarantine lazaret in substitute and add action
* [PHRAS-2674] - Prod grey skin Improvement
* [PHRAS-2775] - Prod - plugin - Publish item in diapo local menu - plugin skeleton improvement.
* [PHRAS-925] - Search Engine improvement for word with dot and hyphen characters
* [PHRAS-2496] - Pre-build vagrant image for Phraseanet and implement it in Phraseanet vagrant file.
* [PHRAS-2637] - Sub definition Task init : select all databases when databases property is not set
* [PHRAS-2670] - Fix notifications slow sql and basket select
* [PHRAS-2672] - Bump videojs version to 7.5
* [PHRAS-2691] - Prod - delete from trash , send deletion by bulk of 3 records
* [PHRAS-2700] - Prod - number of results - Formating the results number
* [PHRAS-2742] - Enhance plugin-skeleton in 4.0
* [PHRAS-2750] - PHPExiftool to handle DJI XMP Tags, Bump exiftool version and switch to original exiftool/exiftool github repository
* [PHRAS-835] - ES - date format timestamp unix, store and search datetime
* [PHRAS-2791] - Embed-bundle - Videojs player serve poster-image property with sub definition permalink
* [PHRAS-2842] - Databases Models - now default audio encodeur is mp3lame
* [PHRAS-2857] - Exclude some collections from quarantine checkers sha256, UUID, filename (AKA exclude Trash)
* [PHRAS-2899] - Quarantine: allow to substitute without selecting target record, (when match only one record).
* [PHRAS-2765] - Translation in Plugin menu locale is now available
* [PHRAS-2929] - bump sinonjs dependency to 1.7.1
* [PHRAS-2728] - Landing page take browser language in account
* [PHRAS-2693] - Collection Sort Sorter is now presented by column
* [PHRAS-2817] - Deploy and Dev with docker is OK
### Bugfix
* [PHRAS-1069] - Dates seems not extracted from iptc
* [PHRAS-1428] - Phraseanet Binaries in configuration not used in some alchemy-fr libraries (AKA text extraction of pdf is NOK)
* [PHRAS-2567] - Registration Form - Term of use link is broken
* [PHRAS-2644] - Searching for stories after applying a document filtering choice gives no results
* [PHRAS-2652] - Fields "Phraseanet::no-source" are pushed to exiftool
* [PHRAS-2682] - Prod - facets display is NOK when switch from basket or thesaurus Tab.
* [PHRAS-2695] - Prod - Grey and White Skins - Browse Baskets: Unable to read the titles
* [PHRAS-2702] - Lightbox - scroller thumbnail Nok
* [PHRAS-2714] - Adding record from the API leaves a copy of the file into the system temporary directory
* [PHRAS-2715] - Embed bundle, border issue on firefox.
* [PHRAS-2716] - Records SetStatus HTTP API malfunction
* [PHRAS-2723] - None information (name, last name etc...) is keep from the Push or a FeedBack user creation form
* [PHRAS-2748] - Some characters into cterms (candidats) leeds to 500 error
* [PHRAS-2754] - Permalink is not (re) activated when record is move from _TRASH_ collection
* [PHRAS-2860] - Generated Subdefs for video Portait are not correctly Oriented
* [PHRAS-2877] - User manipulator does not allow to set a null email
* [PHRAS-2912] - When updating a user informations the wrong field are populated (job and activity inverted)
* [PHRAS-2811] - Cleanning of bad chars in candidats terms
## 4.0.10
Not publish
## 4.0.9
### Adds
@@ -13,11 +163,11 @@
### Fixes
* PHRAS-2491 - Front - Click on facets title (expand/collapse) launched a bad query, due to jquery error.
* PHRAS-2510 - Front - Facets values appear Truncated after 15th character.
* PHRAS-2153 - Front - No user search possible with the field "Company" and field "Country".
* PHRAS-2154 - Front - Bug on Chrome only - selected 1 document instead of all for the feedback.
* PHRAS-2538 - Back - Some MP4 files were not correctly detected by Phraseanet.
- PHRAS-2491 - Front - Click on facets title (expand/collapse) launched a bad query, due to jquery error.
- PHRAS-2510 - Front - Facets values appear Truncated after 15th character.
- PHRAS-2153 - Front - No user search possible with the field "Company" and field "Country".
- PHRAS-2154 - Front - Bug on Chrome only - selected 1 document instead of all for the feedback.
- PHRAS-2538 - Back - Some MP4 files were not correctly detected by Phraseanet.
## 4.0.8

View File

@@ -37,7 +37,7 @@ RUN echo "deb http://deb.debian.org/debian stretch main non-free" > /etc/apt/sou
swftools \
unoconv \
unzip \
xpdf \
poppler-utils \
libreoffice-base-core \
libreoffice-impress \
libreoffice-calc \
@@ -67,6 +67,7 @@ RUN echo "deb http://deb.debian.org/debian stretch main non-free" > /etc/apt/sou
libgsm1-dev \
libfreetype6-dev \
# End FFmpeg
nano \
&& update-locale "LANG=fr_FR.UTF-8 UTF-8" \
&& dpkg-reconfigure --frontend noninteractive locales \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \

View File

@@ -32,9 +32,13 @@ And follow the install steps described at https://docs.phraseanet.com/4.0/en/Adm
## Prerequisites
- docker-compose
- docker-compose >=v1.25.4
- docker >=v18.01-ce
Note about elasticsearch container
Check this link
https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#docker-prod-prerequisites
## Get started
You should review the default env variables defined in `.env` file.
@@ -84,8 +88,9 @@ 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`
- `bin/console worker:execute -m 2` (default)
- `bin/console task-manager:scheduler:run`
- ...
The default parameters allow you to reach the app with : `http://localhost:8082`
@@ -100,6 +105,12 @@ https://hub.docker.com/r/alchemyfr/phraseanet-worker
https://hub.docker.com/r/alchemyfr/phraseanet-nginx
https://hub.docker.com/repository/docker/alchemyfr/phraseanet-db
https://hub.docker.com/repository/docker/alchemyfr/phraseanet-elasticsearch
To use them and not build the images locally, we advise to override the properties in file: env.local
```bash
@@ -108,6 +119,21 @@ PHRASEANET_DOCKER_REGISTRY=alchemyfr
# Tag of the Docker images
PHRASEANET_DOCKER_TAG=
```
or
Pull images before launch docker-compose
#### Tag organisation on docker hub
```latest``` : latest stable version
```4.0``` : latest stable version in 4.0
```4.1``` : latest stable version in 4.1
```4.1.1``` : Phraseanet version 4.1.1
## Development mode
@@ -172,17 +198,20 @@ XDEBUG_REMOTE_HOST=host.docker.internal
Plugins can be installed during build if you set the `PHRASEANET_PLUGINS` env var as follows:
```bash
PHRASEANET_PLUGINS="git@github.com:alchemy-fr/Phraseanet-plugin-webgallery.git"
PHRASEANET_PLUGINS="https://github.com/alchemy-fr/Phraseanet-plugin-expose.git"
# You can optionally precise the branch to install
# If not precised, the main branch will be pulled
PHRASEANET_PLUGINS="git@github.com:alchemy-fr/Phraseanet-plugin-webgallery.git(custom-branch)"
# Plugins are separated by spaces
PHRASEANET_PLUGINS="git@github.com:foo/bar.git(branch-1) git@github.com:baz/42.git"
# Plugins are separated by semicolons
PHRASEANET_PLUGINS="git@github.com:foo/bar.git(branch-1);git@github.com:baz/42.git"
```
> Prefer the HTTPS URL for public repositories, you will not be required to provide your SSH key.
If you install private plugins, make sure you export your SSH private key content in order to allow docker build to access the GIT repository:
Also ensure you're using the SSH URL form (i.e: `git@github.com:alchemy-fr/repo.git`).
```bash
export PHRASEANET_SSH_PRIVATE_KEY=$(cat ~/.ssh/id_rsa)
# or if your private key is protected by a passphrase:

View File

@@ -328,6 +328,14 @@ workers:
password: guest
vhost: /
externalservice:
ginger:
AutoSubtitling:
service_base_url: https://base.uri
token: 39c6011d
transcript_format: text/vtt
subdef_source: preview
user_account:
deleting_policies:
email_confirmation: true

View File

@@ -13,10 +13,6 @@ services:
- ../:/var/alchemy
- .:/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
- ${PHRASEANET_CUSTOM_DIR}:/var/alchemy/Phraseanet/www/custom:rw
builder:
build:
@@ -50,25 +46,12 @@ services:
volumes:
- ../:/var/alchemy
- .:/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_CUSTOM_DIR}:/var/alchemy/Phraseanet/www/custom:rw
- ${PHRASEANET_CACHE_DIR}:/var/alchemy/Phraseanet/cache:rw
- ${PHRASEANET_TMP_DIR}:/var/alchemy/Phraseanet/tmp:rw
worker:
volumes:
- ../:/var/alchemy
- .:/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_CUSTOM_DIR}:/var/alchemy/Phraseanet/www/custom:rw
- ${PHRASEANET_CACHE_DIR}:/var/alchemy/Phraseanet/cache:rw
- ${PHRASEANET_TMP_DIR}:/var/alchemy/Phraseanet/tmp:rw
rabbitmq:
ports:

View File

@@ -14,10 +14,12 @@ services:
- ${PHRASEANET_DATA_DIR}:/var/alchemy/Phraseanet/datas:rw
- ${PHRASEANET_THUMBNAILS_DIR}:/var/alchemy/Phraseanet/www/thumbnails:rw
- ${PHRASEANET_CUSTOM_DIR}:/var/alchemy/Phraseanet/www/custom:rw
- ${PHRASEANET_PLUGINS_DIR}:/var/alchemy/Phraseanet/www/plugins:rw
depends_on:
- phraseanet
environment:
- MAX_BODY_SIZE
- GATEWAY_SEND_TIMEOUT
ports:
- ${PHRASEANET_APP_PORT}:80
@@ -39,9 +41,12 @@ services:
- PHRASEANET_PROJECT_NAME
- MAX_BODY_SIZE
- MAX_INPUT_VARS
- MAX_EXECUTION_TIME
- MAX_INPUT_TIME
- OPCACHE_ENABLED
- SESSION_CACHE_LIMITER
- PHP_LOG_LEVEL
- PHRASEANET_ADMIN_ACCOUNT_ID
- PHRASEANET_ADMIN_ACCOUNT_EMAIL
- PHRASEANET_ADMIN_ACCOUNT_PASSWORD
- PHRASEANET_DB_HOST
@@ -63,14 +68,28 @@ services:
- PHRASEANET_SMTP_SECURE_MODE
- PHRASEANET_SMTP_USER
- PHRASEANET_SMTP_PASSWORD
- PHRASEANET_DOWNLOAD_DIR
- PHRASEANET_LAZARET_DIR
- PHRASEANET_CAPTION_DIR
- PHRASEANET_WORKER_TMP
- LC_MESSAGES=C.UTF-8
- LC_COLLATE=C.UTF-8
- LC_IDENTIFICATION=C.UTF-8
- LANG=C.UTF-8
- LC_MEASUREMENT=C.UTF-8
- LC_CTYPE=C.UTF-8
- LC_TIME=C.UTF-8
- LC_NAME=C.UTF-8
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
- custom_vol:/var/alchemy/Phraseanet/www/custom:rw
- cache_vol:/var/alchemy/Phraseanet/cache:rw
- ${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_CUSTOM_DIR}:/var/alchemy/Phraseanet/www/custom:rw
- ${PHRASEANET_PLUGINS_DIR}:/var/alchemy/Phraseanet/www/plugins:rw
- ${PHRASEANET_CACHE_DIR}:/var/alchemy/Phraseanet/cache:rw
- ${PHRASEANET_TMP_DIR}:/var/alchemy/Phraseanet/tmp:rw
worker:
build:
@@ -93,14 +112,23 @@ services:
- OPCACHE_ENABLED
- SESSION_CACHE_LIMITER
- PHP_LOG_LEVEL
- LC_MESSAGES=C.UTF-8
- LC_COLLATE=C.UTF-8
- LC_IDENTIFICATION=C.UTF-8
- LANG=C.UTF-8
- LC_MEASUREMENT=C.UTF-8
- LC_CTYPE=C.UTF-8
- LC_TIME=C.UTF-8
- LC_NAME=C.UTF-8
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
- custom_vol:/var/alchemy/Phraseanet/www/custom:rw
- cache_vol:/var/alchemy/Phraseanet/cache:rw
- ${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_CUSTOM_DIR}:/var/alchemy/Phraseanet/www/custom:rw
- ${PHRASEANET_CACHE_DIR}:/var/alchemy/Phraseanet/cache:rw
- ${PHRASEANET_TMP_DIR}:/var/alchemy/Phraseanet/tmp:rw
db:
image: $PHRASEANET_DOCKER_REGISTRY/phraseanet-db:$PHRASEANET_DOCKER_TAG
@@ -151,10 +179,12 @@ volumes:
driver: local
custom_vol:
driver: local
plugins_dir:
driver: local
cache_vol:
driver: local
# to be replacer by stdout/stderr
logs_vol:
driver: local
dev_vol:
driver: local
driver: local

View File

@@ -2,6 +2,6 @@
set -xe
cat /nginx.conf.sample | sed "s/\$MAX_BODY_SIZE/$MAX_BODY_SIZE/g" > /etc/nginx/conf.d/default.conf
cat /nginx.conf.sample | sed "s/\$MAX_BODY_SIZE/$MAX_BODY_SIZE/g" | sed "s/\$GATEWAY_SEND_TIMEOUT/$GATEWAY_SEND_TIMEOUT/g" > /etc/nginx/conf.d/default.conf
exec "$@"

View File

@@ -1,3 +1,4 @@
send_timeout $GATEWAY_SEND_TIMEOUT;
upstream backend {
server phraseanet:9000;
}

View File

@@ -12,26 +12,33 @@ chown -R app:app \
datas \
tmp \
logs \
www/thumbnails \
www/custom
www
FILE=config/configuration.yml
if [ -f "$FILE" ]; then
echo "$FILE exists, skip setup."
bin/setup system:config set registry.general.title $PHRASEANET_PROJECT_NAME
if [[ $PHRASEANET_SMTP_ENABLED=true ]]; then
if [[ $PHRASEANET_PROJECT_NAME ]]; then
bin/setup system:config set registry.general.title $PHRASEANET_PROJECT_NAME
fi
if [[ $PHRASEANET_SMTP_ENABLED && $PHRASEANET_SMTP_ENABLED=true ]]; then
bin/setup system:config set registry.email.smtp-enabled $PHRASEANET_SMTP_ENABLED
bin/setup system:config set registry.email.smtp-auth-enabled $PHRASEANET_SMTP_AUTH_ENABLED
bin/setup system:config set registry.email.smtp-auth-secure-mode $PHRASEANET_SMTP_SECURE_MODE
bin/setup system:config set registry.email.smtp-auth-host $PHRASEANET_SMTP_HOST
bin/setup system:config set registry.email.smtp-auth-port $PHRASEANET_SMTP_PORT
bin/setup system:config set registry.email.smtp-host $PHRASEANET_SMTP_HOST
bin/setup system:config set registry.email.smtp-port $PHRASEANET_SMTP_PORT
bin/setup system:config set registry.email.smtp-user $PHRASEANET_SMTP_USER
bin/setup system:config set registry.email.smtp-password $PHRASEANET_SMTP_PASSWORD
bin/setup system:config set registry.email.emitter-email $PHRASEANET_EMITTER_EMAIL
bin/setup system:config set registry.email.prefix $PHRASEANET_MAIL_OBJECT_PREFIX
if [[ -n $PHRASEANET_TRUSTED_PROXY ]]; then
bin/setup system:config add trusted-proxies $PHRASEANET_TRUSTED_PROXY
fi
fi
bin/console user:password --user_id=1 --password $PHRASEANET_ADMIN_ACCOUNT_PASSWORD -y
if [[ -n ${PHRASEANET_ADMIN_ACCOUNT_ID} && $PHRASEANET_ADMIN_ACCOUNT_ID =~ ^[0-9]+$ ]]; then
bin/console user:password --user_id=$PHRASEANET_ADMIN_ACCOUNT_ID --password $PHRASEANET_ADMIN_ACCOUNT_PASSWORD -y
fi
else
echo "$FILE doesn't exist, entering setup..."
runuser app -c docker/phraseanet/auto-install.sh
@@ -43,5 +50,18 @@ if [ ${XDEBUG_ENABLED} == "1" ]; then
fi
./docker/phraseanet/plugins/console init
#rm -Rf cache/
chown -R app:app \
cache \
config \
datas \
tmp \
logs \
www
if [ -d "plugins/" ];then
chown -R app:app plugins;
fi
bash -e docker-php-entrypoint $@

View File

@@ -380,7 +380,7 @@ expose_php = On
; Maximum execution time of each script, in seconds
; http://php.net/max-execution-time
; Note: This directive is hardcoded to 0 for the CLI SAPI
max_execution_time = 9999
max_execution_time = $MAX_EXECUTION_TIME
; Maximum amount of time each script may spend parsing request data. It's a good
; idea to limit this time on productions servers in order to eliminate unexpectedly
@@ -390,7 +390,7 @@ max_execution_time = 9999
; Development Value: 60 (60 seconds)
; Production Value: 60 (60 seconds)
; http://php.net/max-input-time
max_input_time = 60
max_input_time = $MAX_INPUT_TIME
; Maximum input variable nesting level
; http://php.net/max-input-nesting-level

View File

@@ -29,7 +29,7 @@ class InstallCommand extends Command
mkdir($pluginsDir);
}
foreach (explode(' ', $plugins) as $key => $plugin) {
foreach (explode(';', $plugins) as $key => $plugin) {
$plugin = trim($plugin);
$repo = $plugin;
$branch = 'master';

View File

@@ -91,7 +91,6 @@ use Alchemy\Phrasea\WorkerManager\Provider\AlchemyWorkerServiceProvider;
use Alchemy\Phrasea\WorkerManager\Provider\QueueWorkerServiceProvider;
use Alchemy\QueueProvider\QueueServiceProvider;
use Alchemy\WorkerProvider\WorkerServiceProvider;
use Doctrine\DBAL\Event\ConnectionEventArgs;
use MediaVorus\Media\MediaInterface;
use MediaVorus\MediaVorus;
use Monolog\Handler\ErrorLogHandler;
@@ -618,7 +617,7 @@ class Application extends SilexApplication
);
$this['tmp.lazaret.path'] = $factory->createDefinition(
['main', 'storage', 'quarantine'],
['main', 'storage', 'lazaret'],
function (Application $app) {
return $app['tmp.path'].'/lazaret';
}

View File

@@ -2093,7 +2093,7 @@ class V1Controller extends Controller
try {
$collection = \collection::getByBaseId($this->app, $request->get('base_id'));
$record->move_to_collection($collection, $this->getApplicationBox());
$record->move_to_collection($collection);
return Result::create($request, ["record" => $this->listRecord($request, $record)])->createResponse();
} catch (\Exception $e) {

View File

@@ -0,0 +1,393 @@
<?php
namespace Alchemy\Phrasea\Controller\Api\V3;
use Alchemy\Phrasea\Application\Helper\DispatcherAware;
use Alchemy\Phrasea\Application\Helper\JsonBodyAware;
use Alchemy\Phrasea\Controller\Api\Result;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Core\Event\RecordEdit;
use Alchemy\Phrasea\Core\PhraseaEvents;
use caption_field;
use databox_field;
use Exception;
use record_adapter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class V3RecordController extends Controller
{
use JsonBodyAware;
use DispatcherAware;
/**
* Return detailed information about one story
*
* @param Request $request
* @param int $databox_id
* @param int $record_id
*
* @return Response
*/
public function indexAction_patch(Request $request, $databox_id, $record_id)
{
$struct = $this->findDataboxById($databox_id)->get_meta_structure();
$record = $this->findDataboxById($databox_id)->get_record($record_id);
//$record->set_metadatas()
//setRecordStatusAction
try {
$b = $this->decodeJsonBody($request);
}
catch (Exception $e) {
return $this->app['controller.api.v1']->getBadRequestAction($request, 'Bad JSON');
}
$debug = [
'metadatas_ops' => null,
'sb_ops' => null,
];
try {
// do metadatas ops
if (is_array($b->metadatas)) {
$debug['metadatas_ops'] = $this->do_metadatas($struct, $record, $b->metadatas);
}
// do sb ops
if (is_array($b->status)) {
$debug['sb_ops'] = $this->do_status($record, $b->status);
}
if(!is_null($b->base_id)) {
$debug['coll_ops'] = $this->do_collection($record, $b->base_id);
}
}
catch (Exception $e) {
return $this->app['controller.api.v1']->getBadRequestAction(
$request,
$e->getMessage()
);
}
// @todo Move event dispatch inside record_adapter class (keeps things encapsulated)
$this->dispatch(PhraseaEvents::RECORD_EDIT, new RecordEdit($record));
$ret = $this->getResultHelpers()->listRecord($request, $record, $this->getAclForUser());
return Result::create($request, $ret)->createResponse();
}
/**
* @param record_adapter $record
* @param $base_id
*/
private function do_collection(record_adapter $record, $base_id)
{
$record->move_to_collection($this->getApplicationBox()->get_collection($base_id));
}
//////////////////////////////////
/// TODO : keep multi-values uniques !
/// it should be done in record_adapter
//////////////////////////////////
/**
* @param databox_field[] $struct
* @param record_adapter $record
* @param $metadatas
* @return array
* @throws Exception
*/
private function do_metadatas($struct, record_adapter $record, $metadatas)
{
$structByKey = [];
$allStructFields = [];
foreach ($struct as $f) {
$allStructFields[$f->get_id()] = $f;
$structByKey[$f->get_id()] = &$allStructFields[$f->get_id()];
$structByKey[$f->get_name()] = &$allStructFields[$f->get_id()];
}
$metadatas_ops = [];
foreach ($metadatas as $_m) {
// sanity
if($_m->meta_struct_id && $_m->field_name) {
throw new Exception("define meta_struct_id OR field_name, not both.");
}
// select fields that match meta_struct_id or field_name (can be arrays)
$fields_list = null; // to filter caption_fields from record, default all
$struct_fields = []; // struct fields that match meta_struct_id or field_name
$field_keys = $_m->meta_struct_id ? $_m->meta_struct_id : $_m->field_name; // can be null if none defined (=match all)
if($field_keys !== null) {
if (!is_array($field_keys)) {
$field_keys = [$field_keys];
}
$fields_list = [];
foreach ($field_keys as $k) {
if(array_key_exists($k, $structByKey)) {
$fields_list[] = $structByKey[$k]->get_name();
$struct_fields[$structByKey[$k]->get_id()] = $structByKey[$k];
}
else {
throw new Exception(sprintf("unknown field (%s).", $k));
}
}
}
else {
// no meta_struct_id, no field_name --> match all struct fields !
$struct_fields = $allStructFields;
}
$caption_fields = $record->get_caption()->get_fields($fields_list, true);
$meta_id = is_null($_m->meta_id) ? null : (int)($_m->meta_id);
if(!($match_method = (string)($_m->match_method))) {
$match_method = 'ignore_case';
}
if(!in_array($match_method, ['strict', 'ignore_case', 'regexp'])) {
throw new Exception(sprintf("bad match_method (%s).", $match_method));
}
$values = [];
if(is_array($_m->value)) {
foreach ($_m->value as $v) {
if(($v = trim((string)$v)) !== '') {
$values[] = $v;
}
}
}
else {
if(($v = trim((string)($_m->value))) !== '') {
$values[] = $v;
}
}
if(!($action = (string)($_m->action))) {
$action = 'set';
}
switch ($_m->action) {
case 'set':
$ops = $this->metadata_set($struct_fields, $caption_fields, $meta_id, $values);
break;
case 'add':
$ops = $this->metadata_add($struct_fields, $values);
break;
case 'delete':
$ops = $this->metadata_replace($caption_fields, $meta_id, $match_method, $values, null);
break;
case 'replace':
if (!is_string($_m->replace_with) && !is_null($_m->replace_with)) {
throw new Exception("bad \"replace_with\" for action \"replace\".");
}
$ops = $this->metadata_replace($caption_fields, $meta_id, $match_method, $values, $_m->replace_with);
break;
default:
throw new Exception(sprintf("bad action (%s).", $action));
}
$metadatas_ops = array_merge($metadatas_ops, $ops);
}
$record->set_metadatas($metadatas_ops, true);
return $metadatas_ops;
}
/**
* @param $record
* @param $statuses
* @return array
* @throws Exception
*/
private function do_status(record_adapter $record, $statuses)
{
$datas = strrev($record->getStatus());
foreach ($statuses as $status) {
$n = (int)($status->bit);
$value = (int)($status->state);
if ($n > 31 || $n < 4) {
throw new Exception(sprintf("Invalid status bit number (%s).", $n));
}
if ($value < 0 || $value > 1) {
throw new Exception(sprintf("Invalid status bit state (%s) for bit (%s).", $value, $n));
}
$datas = substr($datas, 0, ($n)) . $value . substr($datas, ($n + 1));
}
$record->setStatus(strrev($datas));
return ["status" => $this->getResultHelpers()->listRecordStatus($record)];
}
private function match($pattern, $method, $value)
{
switch ($method) {
case 'strict':
return $value === $pattern;
case 'ignore_case':
return strtolower($value) === strtolower($pattern);
case 'regexp':
return preg_match($pattern, $value) == 1;
}
return false;
}
/**
* @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name
* @param caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set)
* @param int|null $meta_id
* @param string[] $values
*
* @return array ops to execute
* @throws Exception
*/
private function metadata_set($struct_fields, $caption_fields, $meta_id, $values)
{
$ops = [];
// if one field was multi-valued and no meta_id was set, we must delete all values
foreach ($caption_fields as $cf) {
foreach ($cf->get_values() as $field_value) {
if (is_null($meta_id) || $field_value->getId() === (int)$meta_id) {
$ops[] = [
'meta_struct_id' => $cf->get_meta_struct_id(),
'meta_id' => $field_value->getId(),
'value' => ''
];
}
}
}
// now set values to matching struct_fields
foreach ($struct_fields as $sf) {
if($sf->is_multi()) {
// add the non-null value(s)
foreach ($values as $value) {
if ($value) {
$ops[] = [
'meta_struct_id' => $sf->get_id(),
'meta_id' => $meta_id, // can be null
'value' => $value
];
}
}
}
else {
// mono-valued
if(count($values) > 1) {
throw new Exception(sprintf("setting mono-valued (%s) requires only one value.", $sf->get_name()));
}
if( ($value = $values[0]) ) {
$ops[] = [
'meta_struct_id' => $sf->get_id(),
'meta_id' => $meta_id, // probably null,
'value' => $value
];
}
}
}
return $ops;
}
/**
* @param databox_field[] $struct_fields struct-fields (from struct) matching meta_struct_id or field_name
* @param string[] $values
*
* @return array ops to execute
* @throws Exception
*/
private function metadata_add($struct_fields, $values)
{
$ops = [];
// now set values to matching struct_fields
foreach ($struct_fields as $sf) {
if(!$sf->is_multi()) {
throw new Exception(sprintf("can't \"add\" to mono-valued (%s).", $sf->get_name()));
}
foreach ($values as $value) {
$ops[] = [
'meta_struct_id' => $sf->get_id(),
'meta_id' => null,
'value' => $value
];
}
}
return $ops;
}
/**
* @param caption_field[] $caption_fields caption-fields (from record) matching meta_struct_id or field_name (or all if not set)
* @param int|null $meta_id
* @param string $match_method "strict" | "ignore_case" | "regexp"
* @param string[] $values
* @param string|null $replace_with
*
* @return array ops to execute
*/
private function metadata_replace($caption_fields, $meta_id, $match_method, $values, $replace_with)
{
$ops = [];
$replace_with = trim((string)$replace_with);
foreach ($caption_fields as $cf) {
// match all ?
if(is_null($meta_id) && count($values) == 0) {
foreach ($cf->get_values() as $field_value) {
$ops[] = [
'meta_struct_id' => $cf->get_meta_struct_id(),
'meta_id' => $field_value->getId(),
'value' => $replace_with
];
}
}
// match by meta-id ?
if (!is_null($meta_id)) {
foreach ($cf->get_values() as $field_value) {
if ($field_value->getId() === $meta_id) {
$ops[] = [
'meta_struct_id' => $cf->get_meta_struct_id(),
'meta_id' => $field_value->getId(),
'value' => $replace_with
];
}
}
}
// match by value(s) ?
foreach ($values as $value) {
foreach ($cf->get_values() as $field_value) {
$rw = $replace_with;
if($match_method=='regexp' && $rw != '') {
$rw = preg_replace($value, $rw, $field_value->getValue());
}
if ($this->match($value, $match_method, $field_value->getValue())) {
$ops[] = [
'meta_struct_id' => $cf->get_meta_struct_id(),
'meta_id' => $field_value->getId(),
'value' => $rw
];
}
}
}
}
return $ops;
}
/**
* @return V3ResultHelpers
*/
private function getResultHelpers()
{
return $this->app['controller.api.v3.resulthelpers'];
}
}

View File

@@ -0,0 +1,277 @@
<?php
namespace Alchemy\Phrasea\Controller\Api\V3;
use ACL;
use Alchemy\Phrasea\Authentication\Authenticator;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
use Alchemy\Phrasea\Media\MediaSubDefinitionUrlGenerator;
use caption_field;
use databox_status;
use media_Permalink_Adapter;
use media_subdef;
use record_adapter;
use Symfony\Component\HttpFoundation\Request;
class V3ResultHelpers
{
/** @var PropertyAccess */
private $conf;
/** @var MediaSubDefinitionUrlGenerator */
private $urlgenerator;
/** @var Authenticator */
private $authenticator;
public function __construct($conf, $urlgenerator, Authenticator $authenticator)
{
$this->urlgenerator = $urlgenerator;
$this->conf = $conf;
$this->authenticator = $authenticator;
}
/**
* Retrieve detailed information about one status
*
* @param record_adapter $record
* @return array
*/
public function listRecordStatus(record_adapter $record)
{
$ret = [];
foreach ($record->getStatusStructure() as $bit => $status) {
$ret[] = [
'bit' => $bit,
'state' => databox_status::bitIsSet($record->getStatusBitField(), $bit),
];
}
return $ret;
}
public function listEmbeddableMedia(Request $request, record_adapter $record, media_subdef $media, ACL $acl)
{
if (!$media->is_physically_present()) {
return null;
}
if ($this->getAuthenticator()->isAuthenticated()) {
if ($media->get_name() !== 'document'
&& false === $acl->has_access_to_subdef($record, $media->get_name())
) {
return null;
}
if ($media->get_name() === 'document'
&& !$acl->has_right_on_base($record->getBaseId(), ACL::CANDWNLDHD)
&& !$acl->has_hd_grant($record)
) {
return null;
}
}
if ($media->get_permalink() instanceof media_Permalink_Adapter) {
$permalink = $this->listPermalink($media->get_permalink());
} else {
$permalink = null;
}
$urlTTL = (int) $request->get(
'subdef_url_ttl',
$this->getConf()->get(['registry', 'general', 'default-subdef-url-ttl'])
);
if ($urlTTL < 0) {
$urlTTL = -1;
}
$issuer = $this->getAuthenticator()->getUser();
return [
'name' => $media->get_name(),
'permalink' => $permalink,
'height' => $media->get_height(),
'width' => $media->get_width(),
'filesize' => $media->get_size(),
'devices' => $media->getDevices(),
'player_type' => $media->get_type(),
'mime_type' => $media->get_mime(),
'substituted' => $media->is_substituted(),
'created_on' => $media->get_creation_date()->format(DATE_ATOM),
'updated_on' => $media->get_modification_date()->format(DATE_ATOM),
'url' => $this->urlgenerator->generate($issuer, $media, $urlTTL),
'url_ttl' => $urlTTL,
];
}
/**
* @param media_Permalink_Adapter $permalink
* @return array
*
* @todo fix duplicated code
* @noinspection DuplicatedCode
*/
public function listPermalink(media_Permalink_Adapter $permalink)
{
$downloadUrl = $permalink->get_url();
$downloadUrl->getQuery()->set('download', '1');
return [
'created_on' => $permalink->get_created_on()->format(DATE_ATOM),
'id' => $permalink->get_id(),
'is_activated' => $permalink->get_is_activated(),
'label' => $permalink->get_label(),
'updated_on' => $permalink->get_last_modified()->format(DATE_ATOM),
'page_url' => $permalink->get_page(),
'download_url' => (string)$downloadUrl,
'url' => (string)$permalink->get_url(),
];
}
/**
* Retrieve detailed information about one record
*
* @param Request $request
* @param record_adapter $record
* @param ACL $aclforuser
* @return array
*/
public function listRecord(Request $request, record_adapter $record, ACL $aclforuser)
{
$technicalInformation = [];
foreach ($record->get_technical_infos()->getValues() as $name => $value) {
$technicalInformation[] = ['name' => $name, 'value' => $value];
}
$data = [
'databox_id' => $record->getDataboxId(),
'record_id' => $record->getRecordId(),
'mime_type' => $record->getMimeType(),
'title' => $record->get_title(),
'original_name' => $record->get_original_name(),
'updated_on' => $record->getUpdated()->format(DATE_ATOM),
'created_on' => $record->getCreated()->format(DATE_ATOM),
'collection_id' => $record->getCollectionId(),
'base_id' => $record->getBaseId(),
'sha256' => $record->getSha256(),
'thumbnail' => $this->listEmbeddableMedia($request, $record, $record->get_thumbnail(), $aclforuser),
'technical_informations' => $technicalInformation,
'phrasea_type' => $record->getType(),
'uuid' => $record->getUuid(),
];
if ($request->attributes->get('_extended', false)) {
$data = array_merge($data, [
'subdefs' => $this->listRecordEmbeddableMedias($request, $record, $aclforuser),
'metadata' => $this->listRecordMetadata($record, $aclforuser),
'status' => $this->listRecordStatus($record),
'caption' => $this->listRecordCaption($record, $aclforuser),
]);
}
return $data;
}
/**
* @param Request $request
* @param record_adapter $record
* @return array
*/
private function listRecordEmbeddableMedias(Request $request, record_adapter $record, ACL $acl)
{
$subdefs = [];
foreach ($record->get_embedable_medias([], []) as $name => $media) {
if (null !== $subdef = $this->listEmbeddableMedia($request, $record, $media, $acl)) {
$subdefs[] = $subdef;
}
}
return $subdefs;
}
/**
* List all fields of given record
*
* @param record_adapter $record
* @param ACL $acl
* @return array
*/
private function listRecordMetadata(record_adapter $record, ACL $acl)
{
$includeBusiness = $acl->can_see_business_fields($record->getDatabox());
return $this->listRecordCaptionFields($record->get_caption()->get_fields(null, $includeBusiness));
}
/**
* @param caption_field[] $fields
* @return array
*/
private function listRecordCaptionFields($fields)
{
$ret = [];
foreach ($fields as $field) {
$databox_field = $field->get_databox_field();
$fieldData = [
'meta_structure_id' => $field->get_meta_struct_id(),
'name' => $field->get_name(),
'labels' => [
'fr' => $databox_field->get_label('fr'),
'en' => $databox_field->get_label('en'),
'de' => $databox_field->get_label('de'),
'nl' => $databox_field->get_label('nl'),
],
];
foreach ($field->get_values() as $value) {
$data = [
'meta_id' => $value->getId(),
'value' => $value->getValue(),
];
$ret[] = $fieldData + $data;
}
}
return $ret;
}
/**
* @param record_adapter $record
* @param ACL $acl
* @return array
*/
private function listRecordCaption(record_adapter $record, ACL $acl)
{
$includeBusiness = $acl->can_see_business_fields($record->getDatabox());
$caption = [];
foreach ($record->get_caption()->get_fields(null, $includeBusiness) as $field) {
$caption[] = [
'meta_structure_id' => $field->get_meta_struct_id(),
'name' => $field->get_name(),
'value' => $field->get_serialized_values(';'),
];
}
return $caption;
}
////////////////////////
private function getAuthenticator()
{
return $this->authenticator;
}
protected function getConf()
{
return $this->conf;
}
}

View File

@@ -1,8 +1,11 @@
<?php
namespace Alchemy\Phrasea\Controller\Api;
namespace Alchemy\Phrasea\Controller\Api\V3;
use Alchemy\Phrasea\Application\Helper\DispatcherAware;
use Alchemy\Phrasea\Application\Helper\JsonBodyAware;
use Alchemy\Phrasea\Collection\Reference\CollectionReference;
use Alchemy\Phrasea\Controller\Api\Result;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Databox\DataboxGroupable;
use Alchemy\Phrasea\Fractal\CallbackTransformer;
@@ -31,34 +34,19 @@ use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
use Alchemy\Phrasea\SearchEngine\SearchEngineLogger;
use Alchemy\Phrasea\SearchEngine\SearchEngineOptions;
use Alchemy\Phrasea\SearchEngine\SearchEngineResult;
use caption_record;
use League\Fractal\Manager as FractalManager;
use League\Fractal\Resource\Item;
use media_Permalink_Adapter;
use media_subdef;
use record_adapter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class V3Controller extends Controller
class V3SearchController extends Controller
{
/**
* Return detailed information about one story
*
* @param Request $request
* @param int $databox_id
* @param int $record_id
*
* @return Response
*/
public function getStoryAction(Request $request, $databox_id, $record_id)
{
try {
$story = $this->findDataboxById($databox_id)->get_record($record_id);
return Result::create($request, ['story' => $this->listStory($request, $story)])->createResponse();
} catch (NotFoundHttpException $e) {
return Result::createError($request, 404, $this->app->trans('Story Not Found'))->createResponse();
} catch (\Exception $e) {
return $this->app['controller.api.v1']->getBadRequestAction($request, $this->app->trans('An error occurred'));
}
}
use JsonBodyAware;
use DispatcherAware;
/**
* Search for results
@@ -101,7 +89,7 @@ class V3Controller extends Controller
$includeResolver = new IncludeResolver($transformerResolver);
$fractal = new \League\Fractal\Manager();
$fractal = new FractalManager();
$fractal->setSerializer(new TraceableArraySerializer($this->app['dispatcher']));
$fractal->parseIncludes($this->resolveSearchIncludes($request));
@@ -125,314 +113,7 @@ class V3Controller extends Controller
return Result::create($request, $ret)->createResponse();
}
/**
* Retrieve detailed information about one story
*
* @param Request $request
* @param \record_adapter $story
* @return array
* @throws \Exception
*/
private function listStory(Request $request, \record_adapter $story)
{
if (!$story->isStory()) {
return Result::createError($request, 404, 'Story not found')->createResponse();
}
$per_page = (int)$request->get('per_page')?:10;
$page = (int)$request->get('page')?:1;
$offset = ($per_page * ($page - 1)) + 1;
$caption = $story->get_caption();
$format = function (\caption_record $caption, $dcField) {
$field = $caption->get_dc_field($dcField);
if (!$field) {
return null;
}
return $field->get_serialized_values();
};
return [
'@entity@' => V1Controller::OBJECT_TYPE_STORY,
'databox_id' => $story->getDataboxId(),
'story_id' => $story->getRecordId(),
'updated_on' => $story->getUpdated()->format(DATE_ATOM),
'created_on' => $story->getCreated()->format(DATE_ATOM),
'collection_id' => $story->getCollectionId(),
'base_id' => $story->getBaseId(),
'thumbnail' => $this->listEmbeddableMedia($request, $story, $story->get_thumbnail()),
'uuid' => $story->getUuid(),
'metadatas' => [
'@entity@' => V1Controller::OBJECT_TYPE_STORY_METADATA_BAG,
'dc:contributor' => $format($caption, \databox_Field_DCESAbstract::Contributor),
'dc:coverage' => $format($caption, \databox_Field_DCESAbstract::Coverage),
'dc:creator' => $format($caption, \databox_Field_DCESAbstract::Creator),
'dc:date' => $format($caption, \databox_Field_DCESAbstract::Date),
'dc:description' => $format($caption, \databox_Field_DCESAbstract::Description),
'dc:format' => $format($caption, \databox_Field_DCESAbstract::Format),
'dc:identifier' => $format($caption, \databox_Field_DCESAbstract::Identifier),
'dc:language' => $format($caption, \databox_Field_DCESAbstract::Language),
'dc:publisher' => $format($caption, \databox_Field_DCESAbstract::Publisher),
'dc:relation' => $format($caption, \databox_Field_DCESAbstract::Relation),
'dc:rights' => $format($caption, \databox_Field_DCESAbstract::Rights),
'dc:source' => $format($caption, \databox_Field_DCESAbstract::Source),
'dc:subject' => $format($caption, \databox_Field_DCESAbstract::Subject),
'dc:title' => $format($caption, \databox_Field_DCESAbstract::Title),
'dc:type' => $format($caption, \databox_Field_DCESAbstract::Type),
],
'records' => $this->listRecords($request, array_values($story->getChildren($offset, $per_page)->get_elements())),
];
}
private function listEmbeddableMedia(Request $request, \record_adapter $record, \media_subdef $media)
{
if (!$media->is_physically_present()) {
return null;
}
if ($this->getAuthenticator()->isAuthenticated()) {
$acl = $this->getAclForUser();
if ($media->get_name() !== 'document'
&& false === $acl->has_access_to_subdef($record, $media->get_name())
) {
return null;
}
if ($media->get_name() === 'document'
&& !$acl->has_right_on_base($record->getBaseId(), \ACL::CANDWNLDHD)
&& !$acl->has_hd_grant($record)
) {
return null;
}
}
if ($media->get_permalink() instanceof \media_Permalink_Adapter) {
$permalink = $this->listPermalink($media->get_permalink());
} else {
$permalink = null;
}
$urlTTL = (int) $request->get(
'subdef_url_ttl',
$this->getConf()->get(['registry', 'general', 'default-subdef-url-ttl'])
);
if ($urlTTL < 0) {
$urlTTL = -1;
}
$issuer = $this->getAuthenticatedUser();
return [
'name' => $media->get_name(),
'permalink' => $permalink,
'height' => $media->get_height(),
'width' => $media->get_width(),
'filesize' => $media->get_size(),
'devices' => $media->getDevices(),
'player_type' => $media->get_type(),
'mime_type' => $media->get_mime(),
'substituted' => $media->is_substituted(),
'created_on' => $media->get_creation_date()->format(DATE_ATOM),
'updated_on' => $media->get_modification_date()->format(DATE_ATOM),
'url' => $this->app['media_accessor.subdef_url_generator']->generate($issuer, $media, $urlTTL),
'url_ttl' => $urlTTL,
];
}
private function listPermalink(\media_Permalink_Adapter $permalink)
{
$downloadUrl = $permalink->get_url();
$downloadUrl->getQuery()->set('download', '1');
return [
'created_on' => $permalink->get_created_on()->format(DATE_ATOM),
'id' => $permalink->get_id(),
'is_activated' => $permalink->get_is_activated(),
/** @Ignore */
'label' => $permalink->get_label(),
'updated_on' => $permalink->get_last_modified()->format(DATE_ATOM),
'page_url' => $permalink->get_page(),
'download_url' => (string)$downloadUrl,
'url' => (string)$permalink->get_url(),
];
}
/**
* @param Request $request
* @param RecordReferenceInterface[]|RecordReferenceCollection $records
* @return array
*/
private function listRecords(Request $request, $records)
{
if (!$records instanceof RecordReferenceCollection) {
$records = new RecordReferenceCollection($records);
}
$technicalData = $this->app['service.technical_data']->fetchRecordsTechnicalData($records);
$data = [];
foreach ($records->toRecords($this->getApplicationBox()) as $index => $record) {
$record->setTechnicalDataSet($technicalData[$index]);
$data[$index] = $this->listRecord($request, $record);
}
return $data;
}
/**
* Retrieve detailed information about one record
*
* @param Request $request
* @param \record_adapter $record
* @return array
*/
private function listRecord(Request $request, \record_adapter $record)
{
$technicalInformation = [];
foreach ($record->get_technical_infos()->getValues() as $name => $value) {
$technicalInformation[] = ['name' => $name, 'value' => $value];
}
$data = [
'databox_id' => $record->getDataboxId(),
'record_id' => $record->getRecordId(),
'mime_type' => $record->getMimeType(),
'title' => $record->get_title(),
'original_name' => $record->get_original_name(),
'updated_on' => $record->getUpdated()->format(DATE_ATOM),
'created_on' => $record->getCreated()->format(DATE_ATOM),
'collection_id' => $record->getCollectionId(),
'base_id' => $record->getBaseId(),
'sha256' => $record->getSha256(),
'thumbnail' => $this->listEmbeddableMedia($request, $record, $record->get_thumbnail()),
'technical_informations' => $technicalInformation,
'phrasea_type' => $record->getType(),
'uuid' => $record->getUuid(),
];
if ($request->attributes->get('_extended', false)) {
$data = array_merge($data, [
'subdefs' => $this->listRecordEmbeddableMedias($request, $record),
'metadata' => $this->listRecordMetadata($record),
'status' => $this->listRecordStatus($record),
'caption' => $this->listRecordCaption($record),
]);
}
return $data;
}
/**
* @param Request $request
* @param \record_adapter $record
* @return array
*/
private function listRecordEmbeddableMedias(Request $request, \record_adapter $record)
{
$subdefs = [];
foreach ($record->get_embedable_medias([], []) as $name => $media) {
if (null !== $subdef = $this->listEmbeddableMedia($request, $record, $media)) {
$subdefs[] = $subdef;
}
}
return $subdefs;
}
/**
* List all fields of given record
*
* @param \record_adapter $record
* @return array
*/
private function listRecordMetadata(\record_adapter $record)
{
$includeBusiness = $this->getAclForUser()->can_see_business_fields($record->getDatabox());
return $this->listRecordCaptionFields($record->get_caption()->get_fields(null, $includeBusiness));
}
/**
* @param \caption_field[] $fields
* @return array
*/
private function listRecordCaptionFields($fields)
{
$ret = [];
foreach ($fields as $field) {
$databox_field = $field->get_databox_field();
$fieldData = [
'meta_structure_id' => $field->get_meta_struct_id(),
'name' => $field->get_name(),
'labels' => [
'fr' => $databox_field->get_label('fr'),
'en' => $databox_field->get_label('en'),
'de' => $databox_field->get_label('de'),
'nl' => $databox_field->get_label('nl'),
],
];
foreach ($field->get_values() as $value) {
$data = [
'meta_id' => $value->getId(),
'value' => $value->getValue(),
];
$ret[] = $fieldData + $data;
}
}
return $ret;
}
/**
* Retrieve detailed information about one status
*
* @param \record_adapter $record
* @return array
*/
private function listRecordStatus(\record_adapter $record)
{
$ret = [];
foreach ($record->getStatusStructure() as $bit => $status) {
$ret[] = [
'bit' => $bit,
'state' => \databox_status::bitIsSet($record->getStatusBitField(), $bit),
];
}
return $ret;
}
/**
* @param \record_adapter $record
* @return array
*/
private function listRecordCaption(\record_adapter $record)
{
$includeBusiness = $this->getAclForUser()->can_see_business_fields($record->getDatabox());
$caption = [];
foreach ($record->get_caption()->get_fields(null, $includeBusiness) as $field) {
$caption[] = [
'meta_structure_id' => $field->get_meta_struct_id(),
'name' => $field->get_name(),
'value' => $field->get_serialized_values(';'),
];
}
return $caption;
}
/**
/**
* Returns requested includes
*
* @param Request $request
@@ -660,7 +341,7 @@ class V3Controller extends Controller
}
/**
* @param RecordCollection|\record_adapter[] $references
* @param RecordCollection|record_adapter[] $references
* @return RecordView[]
*/
private function buildRecordViews($references)
@@ -693,7 +374,7 @@ class V3Controller extends Controller
foreach ($subdefGroups as $index => $subdefGroup) {
if (!isset($subdefGroup['thumbnail'])) {
$fakeSubdef = new \media_subdef($this->app, $references[$index], 'thumbnail', true, []);
$fakeSubdef = new media_subdef($this->app, $references[$index], 'thumbnail', true, []);
$fakeSubdefs[spl_object_hash($fakeSubdef)] = $fakeSubdef;
$subdefGroups[$index]['thumbnail'] = $fakeSubdef;
@@ -701,9 +382,9 @@ class V3Controller extends Controller
}
$allSubdefs = $this->mergeGroupsIntoOneList($subdefGroups);
$allPermalinks = \media_Permalink_Adapter::getMany(
$allPermalinks = media_Permalink_Adapter::getMany(
$this->app,
array_filter($allSubdefs, function (\media_subdef $subdef) use ($fakeSubdefs) {
array_filter($allSubdefs, function (media_subdef $subdef) use ($fakeSubdefs) {
return !isset($fakeSubdefs[spl_object_hash($subdef)]);
})
);
@@ -712,7 +393,7 @@ class V3Controller extends Controller
$subdefViews = [];
/** @var \media_subdef $subdef */
/** @var media_subdef $subdef */
foreach ($allSubdefs as $index => $subdef) {
$subdefView = new SubdefView($subdef);
@@ -728,7 +409,7 @@ class V3Controller extends Controller
$reorderedGroups = [];
/** @var \media_subdef[] $subdefGroup */
/** @var media_subdef[] $subdefGroup */
foreach ($subdefGroups as $index => $subdefGroup) {
$reordered = [];
@@ -789,7 +470,7 @@ class V3Controller extends Controller
/**
* @param RecordView[] $recordViews
* @param \caption_record[] $captions
* @param caption_record[] $captions
* @param bool[] $canSeeBusiness
*/
private function buildCaptionViews($recordViews, $captions, $canSeeBusiness)

View File

@@ -0,0 +1,146 @@
<?php
namespace Alchemy\Phrasea\Controller\Api\V3;
use Alchemy\Phrasea\Application\Helper\DispatcherAware;
use Alchemy\Phrasea\Application\Helper\JsonBodyAware;
use Alchemy\Phrasea\Controller\Api\Result;
use Alchemy\Phrasea\Controller\Api\V1Controller;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Model\RecordReferenceInterface;
use Alchemy\Phrasea\Record\RecordReferenceCollection;
use caption_record;
use databox_Field_DCESAbstract;
use Exception;
use record_adapter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class V3StoriesController extends Controller
{
use JsonBodyAware;
use DispatcherAware;
/**
* Return detailed information about one story
*
* @param Request $request
* @param int $databox_id
* @param int $record_id
*
* @return Response
*/
public function getStoryAction(Request $request, $databox_id, $record_id)
{
try {
$story = $this->findDataboxById($databox_id)->get_record($record_id);
return Result::create($request, ['story' => $this->listStory($request, $story)])->createResponse();
}
catch (NotFoundHttpException $e) {
return Result::createError($request, 404, 'Story Not Found')->createResponse();
}
catch (Exception $e) {
return $this->app['controller.api.v1']->getBadRequestAction($request, 'An error occurred');
}
}
/**
* Retrieve detailed information about one story
*
* @param Request $request
* @param record_adapter $story
* @return array
* @throws Exception
*/
private function listStory(Request $request, record_adapter $story)
{
if (!$story->isStory()) {
return Result::createError($request, 404, 'Story not found')->createResponse();
}
$per_page = (int)$request->get('per_page')?:10;
$page = (int)$request->get('page')?:1;
$offset = ($per_page * ($page - 1)) + 1;
$caption = $story->get_caption();
$format = function (caption_record $caption, $dcField) {
$field = $caption->get_dc_field($dcField);
if (!$field) {
return null;
}
return $field->get_serialized_values();
};
return [
'@entity@' => V1Controller::OBJECT_TYPE_STORY,
'databox_id' => $story->getDataboxId(),
'story_id' => $story->getRecordId(),
'updated_on' => $story->getUpdated()->format(DATE_ATOM),
'created_on' => $story->getCreated()->format(DATE_ATOM),
'collection_id' => $story->getCollectionId(),
'base_id' => $story->getBaseId(),
'thumbnail' => $this->getResultHelpers()->listEmbeddableMedia($request, $story, $story->get_thumbnail(), $this->getAclForUser()),
'uuid' => $story->getUuid(),
'metadatas' => [
'@entity@' => V1Controller::OBJECT_TYPE_STORY_METADATA_BAG,
'dc:contributor' => $format($caption, databox_Field_DCESAbstract::Contributor),
'dc:coverage' => $format($caption, databox_Field_DCESAbstract::Coverage),
'dc:creator' => $format($caption, databox_Field_DCESAbstract::Creator),
'dc:date' => $format($caption, databox_Field_DCESAbstract::Date),
'dc:description' => $format($caption, databox_Field_DCESAbstract::Description),
'dc:format' => $format($caption, databox_Field_DCESAbstract::Format),
'dc:identifier' => $format($caption, databox_Field_DCESAbstract::Identifier),
'dc:language' => $format($caption, databox_Field_DCESAbstract::Language),
'dc:publisher' => $format($caption, databox_Field_DCESAbstract::Publisher),
'dc:relation' => $format($caption, databox_Field_DCESAbstract::Relation),
'dc:rights' => $format($caption, databox_Field_DCESAbstract::Rights),
'dc:source' => $format($caption, databox_Field_DCESAbstract::Source),
'dc:subject' => $format($caption, databox_Field_DCESAbstract::Subject),
'dc:title' => $format($caption, databox_Field_DCESAbstract::Title),
'dc:type' => $format($caption, databox_Field_DCESAbstract::Type),
],
'records' => $this->listRecords($request, array_values($story->getChildren($offset, $per_page)->get_elements())),
];
}
/**
* @param Request $request
* @param RecordReferenceInterface[]|RecordReferenceCollection $records
* @return array
*/
private function listRecords(Request $request, $records)
{
if (!$records instanceof RecordReferenceCollection) {
$records = new RecordReferenceCollection($records);
}
$technicalData = $this->app['service.technical_data']->fetchRecordsTechnicalData($records);
$data = [];
foreach ($records->toRecords($this->getApplicationBox()) as $index => $record) {
$record->setTechnicalDataSet($technicalData[$index]);
$data[$index] = $this->getResultHelpers()->listRecord($request, $record, $this->getAclForUser());
}
return $data;
}
/**
* @return V3ResultHelpers
*/
private function getResultHelpers()
{
return $this->app['controller.api.v3.resulthelpers'];
}
}

View File

@@ -155,6 +155,8 @@ class LanguageController
'description notice' => $translator->trans('prod:mapboxgl: description notice'),
'title-map-dialog' => $translator->trans('prod:mapboxgl: title map dialog'),
'create new user' => $translator->trans('prod:push: create new user'),
'prod:videoeditor:subtitletab:message:: error' => $translator->trans('prod:videoeditor:subtitletab:message:: error'),
'prod:videoeditor:subtitletab:message:: success' => $translator->trans('prod:videoeditor:subtitletab:message:: success'),
]);
}
}

View File

@@ -115,13 +115,13 @@ class MoveCollectionController extends Controller
foreach ($records as $record) {
$oldCollectionId = $record->getCollection()->get_coll_id();
$record->move_to_collection($collection, $this->getApplicationBox());
$record->move_to_collection($collection);
if ($request->request->get("chg_coll_son") == "1") {
/** @var \record_adapter $child */
foreach ($record->getChildren() as $child) {
if ($this->getAclForUser()->has_right_on_base($child->getBaseId(), \ACL::CANDELETERECORD)) {
$child->move_to_collection($collection, $this->getApplicationBox());
$child->move_to_collection($collection);
}
}
}

View File

@@ -242,7 +242,7 @@ class RecordController extends Controller
$this->getEventDispatcher()->dispatch(RecordEvents::DELETE, new DeleteEvent($record));
} else {
// move to trash collection
$record->move_to_collection($trashCollectionsBySbasId[$sbasId], $this->getApplicationBox());
$record->move_to_collection($trashCollectionsBySbasId[$sbasId]);
// disable permalinks
foreach($record->get_subdefs() as $subdef) {
if( ($pl = $subdef->get_permalink()) ) {

View File

@@ -15,8 +15,10 @@ use Alchemy\Phrasea\Application\Helper\FilesystemAware;
use Alchemy\Phrasea\Application\Helper\SubDefinitionSubstituerAware;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Controller\RecordsRequest;
use Alchemy\Phrasea\Core\Event\Record\RecordAutoSubtitleEvent;
use Alchemy\Phrasea\Core\Event\Record\RecordEvents;
use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Exception\RuntimeException;
use Alchemy\Phrasea\Metadata\PhraseanetMetadataReader;
use Alchemy\Phrasea\Metadata\PhraseanetMetadataSetter;
@@ -24,7 +26,6 @@ use Alchemy\Phrasea\Record\RecordWasRotated;
use DataURI\Parser;
use MediaAlchemyst\Alchemyst;
use MediaVorus\MediaVorus;
use PHPExiftool\Reader;
use Symfony\Component\HttpFoundation\Request;
class ToolsController extends Controller
@@ -45,7 +46,6 @@ class ToolsController extends Controller
if (count($records) == 1) {
/** @var \record_adapter $record */
$record = $records->first();
$databox = $record->getDatabox();
/**Array list of subdefs**/
$listsubdef = array_keys($record-> get_subdefs());
@@ -88,14 +88,13 @@ class ToolsController extends Controller
$metadatas = true;
}
}
$conf = $this->getConf();
return $this->render('prod/actions/Tools/index.html.twig', [
'records' => $records,
'record' => $record,
'recordSubdefs' => $recordAccessibleSubdefs,
'metadatas' => $metadatas,
'listsubdef' => $listsubdef
'listsubdef' => $listsubdef
]);
}
@@ -118,6 +117,7 @@ class ToolsController extends Controller
}
foreach ($records as $record) {
/** @var \media_subdef $subdef */
foreach ($record->get_subdefs() as $subdef) {
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
continue;
@@ -146,6 +146,7 @@ class ToolsController extends Controller
foreach ($selection as $record) {
$substituted = false;
/** @var \media_subdef $subdef */
foreach ($record->get_subdefs() as $subdef) {
if ($subdef->is_substituted()) {
$substituted = true;
@@ -362,14 +363,6 @@ class ToolsController extends Controller
return $this->app->json($return);
}
/**
* @return Reader
*/
private function getExifToolReader()
{
return $this->app['exiftool.reader'];
}
/**
* @return Alchemyst
*/
@@ -449,13 +442,45 @@ class ToolsController extends Controller
try {
$record->set_metadatas($metadatas);
}
catch (Exception $e) {
catch (\Exception $e) {
return $this->app->json(['success' => false, 'errorMessage' => $e->getMessage()]);
}
return $this->app->json(['success' => true, 'errorMessage' => '']);
}
public function autoSubtitleAction(Request $request)
{
$record = new \record_adapter($this->app,
(int)$request->request->get("databox_id"),
(int)$request->request->get("record_id")
);
$permalinkUrl = '';
$conf = $this->getConf();
// if subdef_source not set, by default use the preview permalink
$subdefSource = $conf->get(['externalservice', 'ginger', 'AutoSubtitling', 'subdef_source']) ?: 'preview';
if ($this->isPhysicallyPresent($record, $subdefSource) && ($previewLink = $record->get_subdef($subdefSource)->get_permalink()) != null) {
$permalinkUrl = $previewLink->get_url()->__toString();
}
$this->dispatch(
PhraseaEvents::RECORD_AUTO_SUBTITLE,
new RecordAutoSubtitleEvent(
$record,
$permalinkUrl,
$request->request->get("subtitle_language_source"),
$request->request->get("meta_struct_id_source"),
$request->request->get("subtitle_language_destination"),
$request->request->get("meta_struct_id_destination")
)
);
return $this->app->json(["status" => "dispatch"]);
}
public function videoEditorAction(Request $request)
{
$records = RecordsRequest::fromRequest($this->app, $request, false);
@@ -463,6 +488,7 @@ class ToolsController extends Controller
$metadatas = false;
$record = null;
$JSFields = [];
$videoTextTrackFields = [];
if (count($records) == 1) {
/** @var \record_adapter $record */
@@ -480,6 +506,19 @@ class ToolsController extends Controller
'name' => $meta->get_name(),
'_value' => $record->getCaption([$meta->get_name()]),
];
if (preg_match('/^VideoTextTrack(.*)$/iu', $meta->get_name(), $matches) && !empty($matches[1]) && strlen($matches[1]) == 2 ) {
$field['label'] = $matches[1];
$field['meta_struct_id'] = $meta->get_id();
$field['value'] = '';
if ($record->get_caption()->has_field($meta->get_name())) {
$fieldValues = $record->get_caption()->get_field($meta->get_name())->get_values();
$fieldValue = array_pop($fieldValues);
$field['value'] = $fieldValue->getValue();
}
$videoTextTrackFields[$meta->get_id()] = $field;
unset($field);
}
}
if (!$record->isStory()) {
@@ -489,11 +528,23 @@ class ToolsController extends Controller
$conf = $this->getConf();
return $this->render('prod/actions/Tools/videoEditor.html.twig', [
'records' => $records,
'record' => $record,
'videoEditorConfig' => $conf->get(['video-editor']),
'metadatas' => $metadatas,
'JSonFields' => json_encode($JSFields),
'records' => $records,
'record' => $record,
'videoEditorConfig' => $conf->get(['video-editor']),
'metadatas' => $metadatas,
'JSonFields' => json_encode($JSFields),
'videoTextTrackFields' => $videoTextTrackFields
]);
}
private function isPhysicallyPresent(\record_adapter $record, $subdefName)
{
try {
return $record->get_subdef($subdefName)->is_physically_present();
} catch (\Exception $e) {
unset($e);
}
return false;
}
}

View File

@@ -13,6 +13,9 @@ use Alchemy\Phrasea\Application\Helper\DispatcherAware;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Core\Event\Thesaurus as ThesaurusEvent;
use Alchemy\Phrasea\Core\Event\Thesaurus\ThesaurusEvents;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
use Alchemy\Phrasea\WorkerManager\Event\PopulateIndexEvent;
use Alchemy\Phrasea\WorkerManager\Event\WorkerEvents;
use Doctrine\DBAL\Driver\Connection;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -1222,6 +1225,26 @@ class ThesaurusController extends Controller
]);
}
/**
* Order to populate databox
*
* @param Request $request
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function populate(Request $request)
{
$options = $this->getElasticsearchOptions();
$data['host'] = $options->getHost();
$data['port'] = $options->getPort();
$data['indexName'] = $options->getIndexName();
$data['databoxIds'] = [$request->get('databox_id')];
$this->getDispatcher()->dispatch(WorkerEvents::POPULATE_INDEX, new PopulateIndexEvent($data));
return $this->app->json(["status" => "success"]);
}
/**
* @param Request $request
* @return Response
@@ -3031,4 +3054,12 @@ class ThesaurusController extends Controller
{
return $this->app['locales.available'];
}
/**
* @return ElasticsearchOptions
*/
private function getElasticsearchOptions()
{
return $this->app['elasticsearch.options'];
}
}

View File

@@ -3,21 +3,41 @@
namespace Alchemy\Phrasea\ControllerProvider\Api;
use Alchemy\Phrasea\Application as PhraseaApplication;
use Alchemy\Phrasea\Controller\Api\V3Controller;
use Alchemy\Phrasea\Controller\Api\V3\V3RecordController;
use Alchemy\Phrasea\Controller\Api\V3\V3ResultHelpers;
use Alchemy\Phrasea\Controller\Api\V3\V3SearchController;
use Alchemy\Phrasea\Controller\Api\V3\V3StoriesController;
use Alchemy\Phrasea\Core\Event\Listener\OAuthListener;
use Silex\Application;
use Silex\ControllerCollection;
use Silex\ControllerProviderInterface;
use Silex\ServiceProviderInterface;
class V3 extends Api implements ControllerProviderInterface, ServiceProviderInterface
{
const VERSION = '3.0.0';
public function register(Application $app)
{
$app['controller.api.v3'] = $app->share(function (PhraseaApplication $app) {
return (new V3Controller($app));
$app['controller.api.v3.resulthelpers'] = $app->share(function (PhraseaApplication $app) {
return (new V3ResultHelpers(
$app['conf'],
$app['media_accessor.subdef_url_generator'],
$app['authentication']
));
});
$app['controller.api.v3.metadatas'] = $app->share(function (PhraseaApplication $app) {
return (new V3RecordController($app))
->setJsonBodyHelper($app['json.body_helper'])
->setDispatcher($app['dispatcher'])
;
});
$app['controller.api.v3.search'] = $app->share(function (PhraseaApplication $app) {
return (new V3SearchController($app));
});
$app['controller.api.v3.stories'] = $app->share(function (PhraseaApplication $app) {
return (new V3StoriesController($app));
});
}
@@ -36,12 +56,32 @@ class V3 extends Api implements ControllerProviderInterface, ServiceProviderInte
$controllers->before(new OAuthListener());
$controllers->get('/stories/{databox_id}/{record_id}/', 'controller.api.v3:getStoryAction')
/**
* @uses V3StoriesController::getStoryAction()
*/
$controllers->get('/stories/{databox_id}/{record_id}/', 'controller.api.v3.stories:getStoryAction')
->before('controller.api.v1:ensureCanAccessToRecord')
->assert('databox_id', '\d+')
->assert('record_id', '\d+');
$controllers->match('/search/', 'controller.api.v3:searchAction');
/**
* @uses V3SearchController::searchAction()
*/
$controllers->match('/search/', 'controller.api.v3.search:searchAction');
/**
* @uses V3RecordController::indexAction_patch()
*/
$controllers->patch('/records/{databox_id}/{record_id}/', 'controller.api.v3.metadatas:indexAction_patch')
->before('controller.api.v1:ensureCanAccessToRecord')
->before('controller.api.v1:ensureCanModifyRecord')
->assert('databox_id', '\d+')
->assert('record_id', '\d+');
/**
* @uses \Alchemy\Phrasea\Controller\Api\V1Controller::getBadRequestAction()
*/
$controllers->match('/records/{any_id}/{anyother_id}/setmetadatas/', 'controller.api.v1:getBadRequestAction');
return $controllers;
}

View File

@@ -72,6 +72,9 @@ class Tools implements ControllerProviderInterface, ServiceProviderInterface
$controllers->post('/metadata/save/', 'controller.prod.tools:saveMetasAction')
->bind('prod_tools_metadata_save');
$controllers->post('/auto-subtitle/', 'controller.prod.tools:autoSubtitleAction')
->bind('prod_tools_auto_subtitle');
$controllers->get('/videoEditor', 'controller.prod.tools:videoEditorAction');
return $controllers;

View File

@@ -60,6 +60,7 @@ class Thesaurus implements ControllerProviderInterface, ServiceProviderInterface
$controllers->match('newterm.php', 'controller.thesaurus:newTerm');
$controllers->match('properties.php', 'controller.thesaurus:properties');
$controllers->match('thesaurus.php', 'controller.thesaurus:thesaurus')->bind('thesaurus_thesaurus');
$controllers->match('populate', 'controller.thesaurus:populate')->bind('thesaurus_populate');
$controllers->match('xmlhttp/accept.x.php', 'controller.thesaurus:acceptXml');
$controllers->match('xmlhttp/acceptcandidates.x.php', 'controller.thesaurus:acceptCandidatesXml');

View File

@@ -0,0 +1,57 @@
<?php
namespace Alchemy\Phrasea\Core\Event\Record;
use Alchemy\Phrasea\Model\RecordInterface;
class RecordAutoSubtitleEvent extends RecordEvent
{
private $languageSource;
private $metaStructureIdSource;
private $languageDestination;
private $metaStructureIdDestination;
private $permalinkUrl;
public function __construct(
RecordInterface $record,
$permalinkUrl,
$languageSource,
$metaStructureIdSource,
$languageDestination,
$metaStructureIdDestination
)
{
parent::__construct($record);
$this->languageSource = $languageSource;
$this->metaStructureIdSource = $metaStructureIdSource;
$this->languageDestination = $languageDestination;
$this->metaStructureIdDestination = $metaStructureIdDestination;
$this->permalinkUrl = $permalinkUrl;
}
public function getLanguageSource()
{
return $this->languageSource;
}
public function getMetaStructureIdSource()
{
return $this->metaStructureIdSource;
}
public function getLanguageDestination()
{
return $this->languageDestination;
}
public function getMetaStructureIdDestination()
{
return $this->metaStructureIdDestination;
}
public function getPermalinkUrl()
{
return $this->permalinkUrl;
}
}

View File

@@ -5,7 +5,6 @@ namespace Alchemy\Phrasea\Core\Event\Subscriber;
use Alchemy\Phrasea\Core\Event\Record\RecordEvents;
use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreatedEvent;
use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreationFailedEvent;
use Alchemy\Phrasea\Core\Event\Record\SubDefinitionsCreatedEvent;
use Alchemy\Phrasea\Model\Entities\WebhookEvent;
use Silex\Application;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -30,7 +29,8 @@ class WebhookSubdefEventSubscriber implements EventSubscriberInterface
$this->app['manipulator.webhook-event']->create(
WebhookEvent::RECORD_SUBDEF_CREATED,
WebhookEvent::RECORD_SUBDEF_TYPE,
$eventData
$eventData,
[$event->getRecord()->getBaseId()]
);
}
@@ -45,22 +45,8 @@ class WebhookSubdefEventSubscriber implements EventSubscriberInterface
$this->app['manipulator.webhook-event']->create(
WebhookEvent::RECORD_SUBDEF_FAILED,
WebhookEvent::RECORD_SUBDEF_TYPE,
$eventData
);
}
public function onSubdefsCreated(SubDefinitionsCreatedEvent $event)
{
$eventData = [
'databox_id' => $event->getRecord()->getDataboxId(),
'record_id' => $event->getRecord()->getRecordId(),
'subdef_count' => count($event->getMedia())
];
$this->app['manipulator.webhook-event']->create(
WebhookEvent::RECORD_SUBDEFS_CREATED,
WebhookEvent::RECORD_SUBDEF_TYPE,
$eventData
$eventData,
[$event->getRecord()->getBaseId()]
);
}
@@ -68,7 +54,6 @@ class WebhookSubdefEventSubscriber implements EventSubscriberInterface
{
return [
RecordEvents::SUB_DEFINITION_CREATED => 'onSubdefCreated',
RecordEvents::SUB_DEFINITIONS_CREATED => 'onSubdefsCreated',
RecordEvents::SUB_DEFINITION_CREATION_FAILED => 'onSubdefCreationFailed'
];
}

View File

@@ -54,6 +54,8 @@ final class PhraseaEvents
const RECORD_EDIT = 'record.edit';
const RECORD_UPLOAD = 'record.upload';
const RECORD_AUTO_SUBTITLE = 'record.auto-subtitle';
const THESAURUS_IMPORTED = 'thesaurus.imported';
const THESAURUS_FIELD_LINKED = 'thesaurus.field-linked';
const THESAURUS_CANDIDATE_ACCEPTED_AS_CONCEPT = 'thesaurus.candidate-accepted-as-concept';

View File

@@ -150,6 +150,9 @@ class RepositoriesServiceProvider implements ServiceProviderInterface
$app['repo.worker-running-job'] = $app->share(function (PhraseaApplication $app) {
return $app['orm.em']->getRepository('Phraseanet:WorkerRunningJob');
});
$app['repo.worker-job'] = $app->share(function (PhraseaApplication $app) {
return $app['orm.em']->getRepository('Phraseanet:WorkerJob');
});
$app['repo.worker-running-populate'] = $app->share(function (PhraseaApplication $app) {
return $app['orm.em']->getRepository('Phraseanet:WorkerRunningPopulate');
});

View File

@@ -16,8 +16,7 @@ class Version
/**
* @var string
*/
private $number = '4.1.0-alpha.29a';
private $number = '4.1.1';
/**
* @var string

View File

@@ -0,0 +1,150 @@
<?php
namespace Alchemy\Phrasea\Model\Entities;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @ORM\Table(name="WorkerJob", indexes={@ORM\Index(name="worker_job_type", columns={"type"})})
* @ORM\Entity(repositoryClass="Alchemy\Phrasea\Model\Repositories\WorkerJobRepository")
*/
class WorkerJob
{
const WAITING = "waiting";
const RUNNING = "running";
const FINISHED = "finished";
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue
*/
private $id;
/**
* @ORM\Column(type="string", name="type")
*/
private $type;
/**
* @ORM\Column(type="json_array", name="data", nullable=false)
*/
private $data;
/**
* @ORM\Column(type="string", name="status")
*/
private $status;
/**
* @Gedmo\Timestampable(on="create")
* @ORM\Column(type="datetime")
*/
private $created;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $started;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $finished;
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
public function setType($type)
{
$this->type = $type;
return $this;
}
public function getType()
{
return $this->type;
}
/**
* @param array $data
*
* @return WorkerJob
*/
public function setData(array $data)
{
$this->data = $data;
return $this;
}
public function getData()
{
return $this->data;
}
public function setStatus($status)
{
$this->status = $status;
return $this;
}
public function getStatus()
{
return $this->status;
}
/**
* @return \DateTime
*/
public function getCreated()
{
return $this->created;
}
/**
* @param \DateTime $finished
* @return $this
*/
public function setFinished(\DateTime $finished)
{
$this->finished = $finished;
return $this;
}
/**
* @return mixed
*/
public function getFinished()
{
return $this->finished;
}
/**
* @param \DateTime $started
* @return $this
*/
public function setStarted(\DateTime $started)
{
$this->started = $started;
return $this;
}
/**
* @return mixed
*/
public function getStarted()
{
return $this->started;
}
}

View File

@@ -20,6 +20,8 @@ class WorkerRunningJob
const FINISHED = 'finished';
const RUNNING = 'running';
const MAX_RESULT = 500;
/**
* @ORM\Column(type="integer")
* @ORM\Id

View File

@@ -0,0 +1,22 @@
<?php
namespace Alchemy\Phrasea\Model\Repositories;
use Doctrine\ORM\EntityRepository;
class WorkerJobRepository extends EntityRepository
{
public function getEntityManager()
{
return parent::getEntityManager();
}
public function reconnect()
{
if($this->_em->getConnection()->ping() === false) {
$this->_em->getConnection()->close();
$this->_em->getConnection()->connect();
}
}
}

View File

@@ -25,7 +25,6 @@ class PermalinkTransformer extends TransformerAbstract
'created_on' => $permalink->get_created_on()->format(DATE_ATOM),
'id' => $permalink->get_id(),
'is_activated' => $permalink->get_is_activated(),
/** @Ignore */
'label' => $permalink->get_label(),
'updated_on' => $permalink->get_last_modified()->format(DATE_ATOM),
'page_url' => $permalink->get_page(),

View File

@@ -11,13 +11,10 @@
namespace Alchemy\Phrasea\SearchEngine\Elastic;
use Alchemy\Phrasea\SearchEngine\Elastic\Exception\MergeException;
use Alchemy\Phrasea\SearchEngine\Elastic\Mapping;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Field;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Flag;
use appbox;
use DateTime;
use igorw;
use Exception;
class RecordHelper
{
@@ -100,31 +97,31 @@ class RecordHelper
$a = explode(';', preg_replace('/\D+/', ';', trim($value)));
switch (count($a)) {
case 1: // yyyy
$date = new \DateTime($a[0] . '-01-01'); // will throw if date is not valid
$date = new DateTime($a[0] . '-01-01'); // will throw if date is not valid
$v_fix = $date->format('Y');
break;
case 2: // yyyy;mm
$date = new \DateTime( $a[0] . '-' . $a[1] . '-01');
$date = new DateTime( $a[0] . '-' . $a[1] . '-01');
$v_fix = $date->format('Y-m');
break;
case 3: // yyyy;mm;dd
$date = new \DateTime($a[0] . '-' . $a[1] . '-' . $a[2]);
$date = new DateTime($a[0] . '-' . $a[1] . '-' . $a[2]);
$v_fix = $date->format('Y-m-d');
break;
case 4:
$date = new \DateTime($a[0] . '-' . $a[1] . '-' . $a[2] . ' ' . $a[3] . ':00:00');
$date = new DateTime($a[0] . '-' . $a[1] . '-' . $a[2] . ' ' . $a[3] . ':00:00');
$v_fix = $date->format('Y-m-d H:i:s');
break;
case 5:
$date = new \DateTime($a[0] . '-' . $a[1] . '-' . $a[2] . ' ' . $a[3] . ':' . $a[4] . ':00');
$date = new DateTime($a[0] . '-' . $a[1] . '-' . $a[2] . ' ' . $a[3] . ':' . $a[4] . ':00');
$v_fix = $date->format('Y-m-d H:i:s');
break;
case 6:
$date = new \DateTime($a[0] . '-' . $a[1] . '-' . $a[2] . ' ' . $a[3] . ':' . $a[4] . ':' . $a[5]);
$date = new DateTime($a[0] . '-' . $a[1] . '-' . $a[2] . ' ' . $a[3] . ':' . $a[4] . ':' . $a[5]);
$v_fix = $date->format('Y-m-d H:i:s');
break;
}
} catch (\Exception $e) {
} catch (Exception $e) {
// no-op, v_fix = null
}
@@ -151,8 +148,16 @@ class RecordHelper
return (bool) $value;
case FieldMapping::TYPE_STRING:
$value = substr($value, 0, 32766); // for lucene limit, before a better solution
return str_replace("\0", '', $value);
$value = str_replace("\0", '', $value); // no null char for lucene !
if( strlen($value) > 32766) { // for lucene limit, before a better solution
for($l=32766; $l > 0; $l--) {
if(ord(substr($value, $l-1, 1)) < 128) {
break;
}
}
$value = substr($value, 0, $l);
}
return $value;
default:
return $value;

View File

@@ -74,7 +74,6 @@ class RecordMoverJob extends AbstractJob
private function processData(Application $app, $row, $logsql)
{
/** @var databox $databox */
$databox = $app->findDataboxById($row['sbas_id']);
$rec = $databox->get_record($row['record_id']);
@@ -83,7 +82,7 @@ class RecordMoverJob extends AbstractJob
// change collection ?
if (array_key_exists('coll', $row)) {
$coll = \collection::getByCollectionId($app, $databox, $row['coll']);
$rec->move_to_collection($coll, $app['phraseanet.appbox']);
$rec->move_to_collection($coll);
if ($logsql) {
$this->log('debug', sprintf("on sbas %s move rid %s to coll %s \n", $row['sbas_id'], $row['record_id'], $coll->get_coll_id()));
}

View File

@@ -23,9 +23,9 @@ class WorkerExecuteCommand extends Command
$this->setDescription('Listen queues define on configuration, launch corresponding service for execution')
->addOption('preserve-payload', 'p', InputOption::VALUE_NONE, 'Preserve temporary payload file')
->addOption('queue-name', '', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'The name of queues to be consuming')
->addOption('max-processes', 'm', InputOption::VALUE_REQUIRED, 'The max number of process allow to run (default 4) ')
->addOption('MWG', '', InputOption::VALUE_NONE, 'Enable MWG metadata compatibility (use only for write metadata service)')
->addOption('clear-metadatas', '', InputOption::VALUE_NONE, 'Delete metadatas from documents if not compliant with Database structure (use only for write metadata service)')
->addOption('max-processes', 'm', InputOption::VALUE_REQUIRED, 'The max number of process allow to run (default 1) ')
// ->addOption('MWG', '', InputOption::VALUE_NONE, 'Enable MWG metadata compatibility (use only for write metadata service)')
// ->addOption('clear-metadatas', '', InputOption::VALUE_NONE, 'Remove metadatas from documents if not compliant with Database structure (use only for write metadata service)')
->setHelp('');
return $this;
@@ -33,9 +33,6 @@ class WorkerExecuteCommand extends Command
protected function doExecute(InputInterface $input, OutputInterface $output)
{
$MWG = false;
$clearMetadatas = false;
$argQueueName = $input->getOption('queue-name');
$maxProcesses = intval($input->getOption('max-processes'));
@@ -64,14 +61,6 @@ class WorkerExecuteCommand extends Command
$workerInvoker->setMaxProcessPoolValue($maxProcesses);
}
if ($input->getOption('MWG')) {
$MWG = true;
}
if ($input->getOption('clear-metadatas')) {
$clearMetadatas = true;
}
if ($input->getOption('preserve-payload')) {
$workerInvoker->preservePayloads();
}

View File

@@ -79,11 +79,11 @@ class AdminConfigurationController extends Controller
$reload = ($request->query->get('reload')) == 1 ? true : false ;
if ($request->query->get('running') == 1 && $request->query->get('finished') == 1) {
$workerRunningJob = $repoWorker->findAll();
$workerRunningJob = $repoWorker->findBy([], ['id' => 'DESC'], WorkerRunningJob::MAX_RESULT);
} elseif ($request->query->get('running') == 1) {
$workerRunningJob = $repoWorker->findBy(['status' => WorkerRunningJob::RUNNING]);
$workerRunningJob = $repoWorker->findBy(['status' => WorkerRunningJob::RUNNING], ['id' => 'DESC'], WorkerRunningJob::MAX_RESULT);
} elseif ($request->query->get('finished') == 1) {
$workerRunningJob = $repoWorker->findBy(['status' => WorkerRunningJob::FINISHED]);
$workerRunningJob = $repoWorker->findBy(['status' => WorkerRunningJob::FINISHED], ['id' => 'DESC'], WorkerRunningJob::MAX_RESULT);
}
return $this->render('admin/worker-manager/worker_info.html.twig', [
@@ -92,6 +92,22 @@ class AdminConfigurationController extends Controller
]);
}
public function queueMonitorAction(PhraseaApplication $app, Request $request)
{
$reload = ($request->query->get('reload')) == 1 ? true : false ;
/** @var AMQPConnection $serverConnection */
$serverConnection = $app['alchemy_worker.amqp.connection'];
$serverConnection->getChannel();
$serverConnection->declareExchange();
$queuesStatus = $serverConnection->getQueuesStatus();
return $this->render('admin/worker-manager/worker_queue_monitor.html.twig', [
'queuesStatus' => $queuesStatus,
'reload' => $reload
]);
}
public function truncateTableAction(PhraseaApplication $app, Request $request)
{
/** @var WorkerRunningJobRepository $repoWorker */

View File

@@ -11,11 +11,13 @@ use Alchemy\Phrasea\WorkerManager\Worker\CreateRecordWorker;
use Alchemy\Phrasea\WorkerManager\Worker\DeleteRecordWorker;
use Alchemy\Phrasea\WorkerManager\Worker\ExportMailWorker;
use Alchemy\Phrasea\WorkerManager\Worker\Factory\CallableWorkerFactory;
use Alchemy\Phrasea\WorkerManager\Worker\MainQueueWorker;
use Alchemy\Phrasea\WorkerManager\Worker\PopulateIndexWorker;
use Alchemy\Phrasea\WorkerManager\Worker\ProcessPool;
use Alchemy\Phrasea\WorkerManager\Worker\PullAssetsWorker;
use Alchemy\Phrasea\WorkerManager\Worker\Resolver\TypeBasedWorkerResolver;
use Alchemy\Phrasea\WorkerManager\Worker\SubdefCreationWorker;
use Alchemy\Phrasea\WorkerManager\Worker\SubtitleWorker;
use Alchemy\Phrasea\WorkerManager\Worker\WebhookWorker;
use Alchemy\Phrasea\WorkerManager\Worker\WorkerInvoker;
use Alchemy\Phrasea\WorkerManager\Worker\WriteMetadatasWorker;
@@ -128,6 +130,16 @@ class AlchemyWorkerServiceProvider implements PluginProviderInterface
return (new DeleteRecordWorker())
->setApplicationBox($app['phraseanet.appbox']);
}));
$app['alchemy_worker.type_based_worker_resolver']->addFactory(MessagePublisher::SUBTITLE_TYPE, new CallableWorkerFactory(function () use ($app) {
return (new SubtitleWorker($app['repo.worker-job'], $app['conf'], new LazyLocator($app, 'phraseanet.appbox'), $app['alchemy_worker.logger']))
->setFileSystemLocator(new LazyLocator($app, 'filesystem'))
->setTemporaryFileSystemLocator(new LazyLocator($app, 'temporary-filesystem'));
}));
$app['alchemy_worker.type_based_worker_resolver']->addFactory(MessagePublisher::MAIN_QUEUE_TYPE, new CallableWorkerFactory(function () use ($app) {
return new MainQueueWorker($app['alchemy_worker.message.publisher'], $app['repo.worker-job']);
}));
}
/**

View File

@@ -84,6 +84,10 @@ class ControllerServiceProvider implements ControllerProviderInterface, ServiceP
->method('GET|POST')
->bind('worker_admin_pullAssets');
$controllers->match('/queue-monitor', 'controller.worker.admin.configuration:queueMonitorAction')
->method('GET')
->bind('worker_admin_queue_monitor');
return $controllers;
}

View File

@@ -23,6 +23,7 @@ use Alchemy\Phrasea\WorkerManager\Subscriber\AssetsIngestSubscriber;
use Alchemy\Phrasea\WorkerManager\Subscriber\ExportSubscriber;
use Alchemy\Phrasea\WorkerManager\Subscriber\RecordSubscriber;
use Alchemy\Phrasea\WorkerManager\Subscriber\SearchengineSubscriber;
use Alchemy\Phrasea\WorkerManager\Subscriber\SubtitleSubscriber;
use Alchemy\Phrasea\WorkerManager\Subscriber\WebhookSubscriber;
use Silex\Application;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -68,6 +69,7 @@ class QueueWorkerServiceProvider implements PluginProviderInterface
$dispatcher->addSubscriber(new AssetsIngestSubscriber($app['alchemy_worker.message.publisher']));
$dispatcher->addSubscriber(new SearchengineSubscriber($app['alchemy_worker.message.publisher']));
$dispatcher->addSubscriber(new WebhookSubscriber($app['alchemy_worker.message.publisher']));
$dispatcher->addSubscriber(new SubtitleSubscriber(new LazyLocator($app, 'repo.worker-job'), $app['alchemy_worker.message.publisher']));
return $dispatcher;
})

View File

@@ -29,7 +29,9 @@ class AMQPConnection
MessagePublisher::CREATE_RECORD_TYPE => MessagePublisher::CREATE_RECORD_QUEUE,
MessagePublisher::PULL_QUEUE => MessagePublisher::PULL_QUEUE,
MessagePublisher::POPULATE_INDEX_TYPE => MessagePublisher::POPULATE_INDEX_QUEUE,
MessagePublisher::DELETE_RECORD_TYPE => MessagePublisher::DELETE_RECORD_QUEUE
MessagePublisher::DELETE_RECORD_TYPE => MessagePublisher::DELETE_RECORD_QUEUE,
MessagePublisher::MAIN_QUEUE_TYPE => MessagePublisher::MAIN_QUEUE,
MessagePublisher::SUBTITLE_TYPE => MessagePublisher::SUBTITLE_QUEUE
];
// the corresponding worker queues and retry queues, loop queue
@@ -44,7 +46,6 @@ class AMQPConnection
MessagePublisher::PULL_QUEUE => MessagePublisher::LOOP_PULL_QUEUE
];
// default message TTL in retry queue in millisecond
public static $defaultFailedQueues = [
MessagePublisher::WRITE_METADATAS_TYPE => MessagePublisher::FAILED_METADATAS_QUEUE,
MessagePublisher::SUBDEF_CREATION_TYPE => MessagePublisher::FAILED_SUBDEF_QUEUE,
@@ -213,6 +214,37 @@ class AMQPConnection
}
}
/**
* Get queueName, messageCount, consumerCount of queues
* @return array
*/
public function getQueuesStatus()
{
$queuesList = array_merge(
array_values(self::$defaultQueues),
array_values(self::$defaultDelayedQueues),
array_values(self::$defaultRetryQueues),
array_values(self::$defaultFailedQueues)
);
$this->getChannel();
$queuesStatus = [];
foreach ($queuesList as $queue) {
$this->setQueue($queue);
list($queueName, $messageCount, $consumerCount) = $this->channel->queue_declare($queue, true);
$status['queueName'] = $queueName;
$status['messageCount'] = $messageCount;
$status['consumerCount'] = $consumerCount;
$queuesStatus[] = $status;
unset($status);
}
return $queuesStatus;
}
public function connectionClose()
{
$this->channel->close();

View File

@@ -18,6 +18,12 @@ class MessagePublisher
const WEBHOOK_TYPE = 'webhook';
const POPULATE_INDEX_TYPE = 'populateIndex';
const PULL_ASSETS_TYPE = 'pullAssets';
const SUBTITLE_TYPE = 'subtitle';
const MAIN_QUEUE_TYPE = 'mainQueue';
const MAIN_QUEUE = 'main-queue';
const SUBTITLE_QUEUE = 'subtitle-queue';
// worker queue to be consumed, when no ack , it is requeued to the retry queue
const EXPORT_QUEUE = 'export-queue';

View File

@@ -54,18 +54,20 @@ class RecordSubscriber implements EventSubscriberInterface
if (!$record->isStory()) {
$subdefs = $record->getDatabox()->get_subdef_structure()->getSubdefGroup($record->getType());
foreach ($subdefs as $subdef) {
$payload = [
'message_type' => MessagePublisher::SUBDEF_CREATION_TYPE,
'payload' => [
'recordId' => $event->getRecord()->getRecordId(),
'databoxId' => $event->getRecord()->getDataboxId(),
'subdefName' => $subdef->get_name(),
'status' => $event->isNewRecord() ? MessagePublisher::NEW_RECORD_MESSAGE : ''
]
];
if ($subdefs !== null) {
foreach ($subdefs as $subdef) {
$payload = [
'message_type' => MessagePublisher::SUBDEF_CREATION_TYPE,
'payload' => [
'recordId' => $event->getRecord()->getRecordId(),
'databoxId' => $event->getRecord()->getDataboxId(),
'subdefName' => $subdef->get_name(),
'status' => $event->isNewRecord() ? MessagePublisher::NEW_RECORD_MESSAGE : ''
]
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::SUBDEF_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::SUBDEF_QUEUE);
}
}
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Alchemy\Phrasea\WorkerManager\Subscriber;
use Alchemy\Phrasea\Core\Event\Record\RecordAutoSubtitleEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Model\Entities\WorkerJob;
use Alchemy\Phrasea\Model\Repositories\WorkerJobRepository;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class SubtitleSubscriber implements EventSubscriberInterface
{
private $messagePublisher;
/** @var WorkerJobRepository $repoWorkerJob*/
private $repoWorkerJob;
/** @var callable */
private $repoWorkerJobLocator;
public function __construct(callable $repoWorkerJobLocator, MessagePublisher $messagePublisher)
{
$this->repoWorkerJobLocator = $repoWorkerJobLocator;
$this->messagePublisher = $messagePublisher;
}
public function onRecordAutoSubtitle(RecordAutoSubtitleEvent $event)
{
$this->repoWorkerJob = $this->getRepoWorkerJob();
$em = $this->repoWorkerJob->getEntityManager();
$data = [
"databoxId" => $event->getRecord()->getDataboxId(),
"recordId" => $event->getRecord()->getRecordId(),
"permalinkUrl" => $event->getPermalinkUrl(),
"languageSource" => $event->getLanguageSource(),
"metaStructureIdSource" => $event->getMetaStructureIdSource(),
"languageDestination" => $event->getLanguageDestination(),
"metaStructureIdDestination" => $event->getMetaStructureIdDestination()
];
$this->repoWorkerJob->reconnect();
$em->beginTransaction();
try {
$workerJob = new WorkerJob();
$workerJob
->setType(MessagePublisher::SUBTITLE_TYPE)
->setData($data)
->setStatus(WorkerJob::WAITING)
;
$em->persist($workerJob);
$em->flush();
$em->commit();
$data['workerId'] = $workerJob->getId();
$data['type'] = MessagePublisher::SUBTITLE_TYPE;
$payload = [
'message_type' => MessagePublisher::MAIN_QUEUE_TYPE,
'payload' => $data
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::MAIN_QUEUE);
} catch (\Exception $e) {
$em->rollback();
}
}
public static function getSubscribedEvents()
{
return [
PhraseaEvents::RECORD_AUTO_SUBTITLE => 'onRecordAutoSubtitle',
];
}
/**
* @return WorkerJobRepository
*/
private function getRepoWorkerJob()
{
$callable = $this->repoWorkerJobLocator;
return $callable();
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Alchemy\Phrasea\WorkerManager\Worker;
use Alchemy\Phrasea\Model\Repositories\WorkerJobRepository;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
class MainQueueWorker implements WorkerInterface
{
private $messagePublisher;
private $repoWorkerJob;
public function __construct(
MessagePublisher $messagePublisher,
WorkerJobRepository $repoWorkerJob
)
{
$this->messagePublisher = $messagePublisher;
$this->repoWorkerJob = $repoWorkerJob;
}
public function process(array $payload)
{
// if needed do treatement here depending on the type
$queue = null;
$messageType = '';
switch ($payload['type']) {
case MessagePublisher::SUBTITLE_TYPE:
$queue = MessagePublisher::SUBTITLE_QUEUE;
$messageType = $payload['type'];
unset($payload['type']);
break;
}
$data = [
'message_type' => $messageType,
'payload' => $payload
];
if ($queue != null) {
$this->messagePublisher->publishMessage($data, $queue);
}
}
}

View File

@@ -10,7 +10,7 @@ use Symfony\Component\Process\ProcessBuilder;
class ProcessPool implements LoggerAwareInterface
{
const MAX_PROCESSES = 4;
const MAX_PROCESSES = 1;
/**
* @var int
@@ -41,7 +41,7 @@ class ProcessPool implements LoggerAwareInterface
* Sets a logger instance on the object
*
* @param LoggerInterface $logger
* @return null
* @return void
*/
public function setLogger(LoggerInterface $logger)
{

View File

@@ -0,0 +1,310 @@
<?php
namespace Alchemy\Phrasea\WorkerManager\Worker;
use Alchemy\Phrasea\Application\Helper\FilesystemAware;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
use Alchemy\Phrasea\Model\Entities\WorkerJob;
use Alchemy\Phrasea\Model\Repositories\WorkerJobRepository;
use GuzzleHttp\Client;
use Psr\Log\LoggerInterface;
class SubtitleWorker implements WorkerInterface
{
use FilesystemAware;
/**
* @var callable
*/
private $appboxLocator;
private $logger;
private $conf;
/** @var WorkerJobRepository $repoWorkerJob*/
private $repoWorkerJob;
public function __construct(WorkerJobRepository $repoWorkerJob, PropertyAccess $conf, callable $appboxLocator, LoggerInterface $logger)
{
$this->repoWorkerJob = $repoWorkerJob;
$this->conf = $conf;
$this->appboxLocator = $appboxLocator;
$this->logger = $logger;
}
public function process(array $payload)
{
$gingaBaseurl = $this->conf->get(['externalservice', 'ginger', 'AutoSubtitling', 'service_base_url']);
$gingaToken = $this->conf->get(['externalservice', 'ginger', 'AutoSubtitling', 'token']);
$gingaTranscriptFormat = $this->conf->get(['externalservice', 'ginger', 'AutoSubtitling', 'transcript_format']);
if (!$gingaBaseurl || !$gingaToken || !$gingaTranscriptFormat) {
$this->logger->error("External service Ginga not set correctly in configuration.yml");
return 0;
}
/** @var WorkerJob $workerJob */
$workerJob = $this->repoWorkerJob->find($payload['workerId']);
if ($workerJob == null) {
$this->logger->error("WorkerId not found");
return 0;
}
$workerJob->setStatus(WorkerJob::RUNNING)
->setStarted(new \DateTime('now'));
$em = $this->repoWorkerJob->getEntityManager();
$this->repoWorkerJob->reconnect();
$em->persist($workerJob);
$em->flush();
switch ($gingaTranscriptFormat) {
case 'text/srt,':
$extension = 'srt';
break;
case 'text/plain':
$extension = 'txt';
break;
case 'application/json':
$extension = 'json';
break;
case 'text/vtt':
default:
$extension = 'vtt';
break;
}
$languageSource = $this->getLanguageFormat($payload['languageSource']);
$languageDestination = $this->getLanguageFormat($payload['languageDestination']);
$record = $this->getApplicationBox()->get_databox($payload['databoxId'])->get_record($payload['recordId']);
$languageSourceFieldName = $record->getDatabox()->get_meta_structure()->get_element($payload['metaStructureIdSource'])->get_name();
$subtitleSourceTemporaryFile = $this->getTemporaryFilesystem()->createTemporaryFile("subtitle", null, $extension);
$gingerClient = new Client();
// if the languageSourceFieldName do not yet exist, first generate subtitle for it
if ($payload['permalinkUrl'] != '' && !$record->get_caption()->has_field($languageSourceFieldName)) {
try {
$response = $gingerClient->post($gingaBaseurl.'/media/', [
'headers' => [
'Authorization' => 'token '.$gingaToken
],
'json' => [
'url' => $payload['permalinkUrl'],
'language' => $languageSource
]
]);
} catch(\Exception $e) {
$this->logger->error($e->getMessage());
$this->jobFinished($workerJob);
return 0;
}
if ($response->getStatusCode() !== 201) {
$this->logger->error("response status /media/ : ". $response->getStatusCode());
$this->jobFinished($workerJob);
return 0;
}
$responseMediaBody = $response->getBody()->getContents();
$responseMediaBody = json_decode($responseMediaBody,true);
$checkStatus = true;
do {
// first wait 5 second before check subtitling status
sleep(5);
$this->logger->info("bigin to check status");
try {
$response = $gingerClient->get($gingaBaseurl.'/task/'.$responseMediaBody['task_id'].'/', [
'headers' => [
'Authorization' => 'token '.$gingaToken
]
]);
} catch (\Exception $e) {
$checkStatus = false;
break;
}
if ($response->getStatusCode() !== 200) {
$checkStatus = false;
break;
}
$responseTaskBody = $response->getBody()->getContents();
$responseTaskBody = json_decode($responseTaskBody,true);
} while($responseTaskBody['status'] != 'SUCCESS');
if (!$checkStatus) {
$this->logger->error("can't check status");
$this->jobFinished($workerJob);
return 0;
}
try {
$response = $gingerClient->get($gingaBaseurl.'/media/'.$responseMediaBody['media']['uuid'].'/', [
'headers' => [
'Authorization' => 'token '.$gingaToken,
'ACCEPT' => $gingaTranscriptFormat
],
'query' => [
'language' => $languageSource
]
]);
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
$this->jobFinished($workerJob);
return 0;
}
if ($response->getStatusCode() !== 200) {
$this->logger->error("response status /media/uuid : ". $response->getStatusCode());
$this->jobFinished($workerJob);
return 0;
}
$transcriptContent = $response->getBody()->getContents();
$transcriptContent = preg_replace('/WEBVTT/', 'WEBVTT - with cue identifier', $transcriptContent, 1);
// save subtitle on temporary file to use to translate if needed
file_put_contents($subtitleSourceTemporaryFile, $transcriptContent);
$metadatas[0] = [
'meta_struct_id' => (int)$payload['metaStructureIdSource'],
'meta_id' => '',
'value' => $transcriptContent
];
try {
$record->set_metadatas($metadatas);
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
$this->jobFinished($workerJob);
return 0;
}
$this->logger->info("Generate subtitle on language source SUCCESS");
} elseif ($record->get_caption()->has_field($languageSourceFieldName)) {
// get the source subtitle and save it to a temporary file
$fieldValues = $record->get_caption()->get_field($languageSourceFieldName)->get_values();
$fieldValue = array_pop($fieldValues);
file_put_contents($subtitleSourceTemporaryFile, $fieldValue->getValue());
}
if ($payload['metaStructureIdSource'] !== $payload['metaStructureIdDestination']) {
try {
$response = $gingerClient->post($gingaBaseurl.'/translate/', [
'headers' => [
'Authorization' => 'token '.$gingaToken,
'ACCEPT' => $gingaTranscriptFormat
],
'multipart' => [
[
'name' => 'transcript',
'contents' => fopen($subtitleSourceTemporaryFile, 'r')
],
[
'name' => 'transcript_format',
'contents' => $gingaTranscriptFormat,
],
[
'name' => 'language_in',
'contents' => $languageSource,
],
[
'name' => 'language_out',
'contents' => $languageDestination,
]
]
]);
} catch(\Exception $e) {
$this->logger->error($e->getMessage());
$this->jobFinished($workerJob);
return 0;
}
if ($response->getStatusCode() !== 200) {
$this->logger->error("response status /translate/ : ". $response->getStatusCode());
$this->jobFinished($workerJob);
return 0;
}
$transcriptContent = $response->getBody()->getContents();
$transcriptContent = preg_replace('/WEBVTT/', 'WEBVTT - with cue identifier', $transcriptContent, 1);
$metadatas[0] = [
'meta_struct_id' => (int)$payload['metaStructureIdDestination'],
'meta_id' => '',
'value' => $transcriptContent
];
try {
$record->set_metadatas($metadatas);
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
$this->jobFinished($workerJob);
return 0;
}
$this->logger->info("Translate subtitle on language destination SUCCESS");
}
$this->jobFinished($workerJob);
return 0;
}
/**
* @return \appbox
*/
private function getApplicationBox()
{
$callable = $this->appboxLocator;
return $callable();
}
private function jobFinished(WorkerJob $workerJob)
{
$workerJob->setStatus(WorkerJob::FINISHED)
->setFinished(new \DateTime('now'));
$em = $this->repoWorkerJob->getEntityManager();
$this->repoWorkerJob->reconnect();
$em->persist($workerJob);
$em->flush();
}
private function getLanguageFormat($language)
{
switch ($language) {
case 'En':
return 'en-GB';
case 'De':
return 'de-DE';
case 'Fr':
default:
return 'fr-FR';
}
}
}

View File

@@ -522,10 +522,11 @@ class record_adapter implements RecordInterface, cache_cacheableInterface
/**
*
* @param collection $collection
* @param appbox $appbox
* @param appbox $appbox WTF this parm is useless
* @return record_adapter
*
*/
public function move_to_collection(collection $collection, appbox $appbox)
public function move_to_collection(collection $collection, appbox $appbox = null)
{
if ($this->getCollection()->get_base_id() === $collection->get_base_id()) {
return $this;
@@ -1054,10 +1055,13 @@ class record_adapter implements RecordInterface, cache_cacheableInterface
}
}
if (trim($params['meta_id']) !== '') {
$tmp_val = trim($params['value']);
$tmp_val = trim($params['value']);
$caption_field_value = $caption_field->get_value($params['meta_id']);
if (trim($params['meta_id']) !== '') {
if(is_null($caption_field_value = $caption_field->get_value($params['meta_id']))) {
return $this;
}
if ($tmp_val === '') {
$caption_field_value->delete();
@@ -1068,8 +1072,11 @@ class record_adapter implements RecordInterface, cache_cacheableInterface
$caption_field_value->setVocab($vocab, $vocab_id);
}
}
} else {
$caption_field_value = caption_Field_Value::create($this->app, $databox_field, $this, $params['value'], $vocab, $vocab_id);
}
else {
if($tmp_val !== '') {
caption_Field_Value::create($this->app, $databox_field, $this, $params['value'], $vocab, $vocab_id);
}
}
return $this;

View File

@@ -319,5 +319,15 @@ workers:
user: ''
password: ''
vhost: /
externalservice:
ginger:
AutoSubtitling:
service_base_url: https://base.uri
token: 39c6011d
transcript_format: text/vtt
subdef_source: preview
Console_logger_enabled_environments: [test]

View File

@@ -119,7 +119,7 @@
<vcodec>libvpx</vcodec>
</subdef>
<subdef class="preview" name="audiovideowav" downloadable="true" orderable="true" presets="Wave Mono 8 kHz">
<path>{{datapathnoweb}}{{basename}}/subview</path>
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<meta>no</meta>
<mediatype>audio</mediatype>
<audiobitrate>128</audiobitrate>
@@ -131,7 +131,7 @@
<label lang="en">Audio WAVE 8 kHz</label>
</subdef>
<subdef class="preview" name="audiovideomp3" downloadable="true" orderable="true" presets="Normal MP3 128 kbit/s">
<path>{{datapathnoweb}}{{basename}}/subview</path>
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<meta>no</meta>
<mediatype>audio</mediatype>
<audiobitrate>180</audiobitrate>

View File

@@ -0,0 +1,234 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<record>
<path>{{datapathnoweb}}{{basename}}/documents</path>
<subdefs>
<subdefgroup name="image">
<subdef class="preview" name="preview" downloadable="true">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<size>1024</size>
<method>resample</method>
<dpi>72</dpi>
<strip>no</strip>
<quality>75</quality>
<meta>yes</meta>
<devices>screen</devices>
<mediatype>image</mediatype>
<label lang="fr">Prévisualisation</label>
<label lang="en">Preview</label>
</subdef>
<subdef class="thumbnail" name="thumbnail" downloadable="true">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<size>240</size>
<method>resample</method>
<dpi>72</dpi>
<strip>yes</strip>
<quality>75</quality>
<meta>no</meta>
<devices>screen</devices>
<mediatype>image</mediatype>
<label lang="fr">Vignette</label>
<label lang="en">Thumbnail</label>
</subdef>
<subdef class="preview" name="preview_mobile" downloadable="false">
<size>480</size>
<resolution>72</resolution>
<strip>yes</strip>
<quality>75</quality>
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<mediatype>image</mediatype>
<meta>no</meta>
<devices>handheld</devices>
<label lang="fr">Prévisualisation Mobile</label>
<label lang="en">Mobile Preview</label>
</subdef>
<subdef class="thumbnail" name="thumbnail_mobile" downloadable="false">
<size>150</size>
<resolution>72</resolution>
<strip>yes</strip>
<quality>75</quality>
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<mediatype>image</mediatype>
<meta>no</meta>
<devices>handheld</devices>
<label lang="fr">Vignette mobile</label>
<label lang="en">Mobile Thumbnail</label>
</subdef>
</subdefgroup>
<subdefgroup name="video">
<subdef class="thumbnail" name="thumbnail" downloadable="false">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<size>240</size>
<devices>screen</devices>
<mediatype>image</mediatype>
<writeDatas>no</writeDatas>
<label lang="fr">Vignette</label>
<label lang="en">Thumbnail</label>
</subdef>
<subdef class="thumbnail" name="thumbnailgif" downloadable="false">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<size>240</size>
<mediatype>gif</mediatype>
<delay>150</delay>
<devices>screen</devices>
<writeDatas>no</writeDatas>
<label lang="fr">Animation GIF</label>
<label lang="en">GIF Animation</label>
</subdef>
<subdef class="preview" name="preview" downloadable="true">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<size>748</size>
<mediatype>video</mediatype>
<writeDatas>yes</writeDatas>
<acodec>libfdk_aac</acodec>
<vcodec>libx264</vcodec>
<devices>screen</devices>
<bitrate>1000</bitrate>
<audiobitrate>128</audiobitrate>
<audiosamplerate>48000</audiosamplerate>
<fps>25</fps>
<GOPsize>25</GOPsize>
<label lang="fr">Prévisualisation</label>
<label lang="en">Preview</label>
</subdef>
<subdef class="preview" name="preview_webm" downloadable="false">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<size>748</size>
<mediatype>video</mediatype>
<devices>screen</devices>
<bitrate>1000</bitrate>
<audiobitrate>128</audiobitrate>
<audiosamplerate>48000</audiosamplerate>
<acodec>libvorbis</acodec>
<fps>25</fps>
<GOPsize>25</GOPsize>
<vcodec>libvpx</vcodec>
<label lang="fr">Prévisualisation WebM</label>
<label lang="en">WebM Preview</label>
</subdef>
</subdefgroup>
<subdefgroup name="audio">
<subdef class="thumbnail" name="thumbnail" downloadable="true">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<mediatype>image</mediatype>
<size>240</size>
<devices>screen</devices>
<writeDatas>no</writeDatas>
<label lang="fr">Vignette</label>
<label lang="en">Thumbnail</label>
</subdef>
<subdef class="preview" name="preview" downloadable="true">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<mediatype>audio</mediatype>
<writeDatas>yes</writeDatas>
<audiobitrate>128</audiobitrate>
<audiosamplerate>48000</audiosamplerate>
<devices>screen</devices>
<label lang="fr">Prévisualisation</label>
<label lang="en">Preview</label>
</subdef>
<subdef class="preview" name="preview_mobile" downloadable="false">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<mediatype>audio</mediatype>
<devices>handheld</devices>
<label lang="fr">Prévisualisation Mobile</label>
<label lang="en">Mobile Preview</label>
</subdef>
</subdefgroup>
<subdefgroup name="document">
<subdef class="thumbnail" name="thumbnail" downloadable="false">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<mediatype>image</mediatype>
<method>resample</method>
<dpi>72</dpi>
<size>240</size>
<writeDatas>no</writeDatas>
<devices>screen</devices>
<label lang="fr">Vignette</label>
<label lang="en">Thumbnail</label>
</subdef>
<subdef class="preview" name="preview" downloadable="false">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<mediatype>flexpaper</mediatype>
<writeDatas>no</writeDatas>
<devices>screen</devices>
<label lang="fr">Prévisualisation</label>
<label lang="en">Preview</label>
</subdef>
</subdefgroup>
<subdefgroup name="flash">
<subdef class="thumbnail" name="thumbnail" downloadable="false">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<mediatype>image</mediatype>
<size>240</size>
<writeDatas>no</writeDatas>
<method>resample</method>
<dpi>72</dpi>
<devices>screen</devices>
<label lang="fr">Vignette</label>
<label lang="en">Thumbnail</label>
</subdef>
<subdef class="preview" name="preview" downloadable="false">
<path>{{datapathnoweb}}{{basename}}/subdefs</path>
<mediatype>image</mediatype>
<size>800</size>
<writeDatas>no</writeDatas>
<method>resample</method>
<dpi>72</dpi>
<devices>screen</devices>
<label lang="fr">Prévisualisation</label>
<label lang="en">Preview</label>
</subdef>
</subdefgroup>
</subdefs>
<description>
<GDTI src="" index="1" />
<BrandName src="" index="1" />
<ProductName src="XMP-dc:Title" index="1" />
<ValidFromDate src="" index="1" type="date" />
<GTIN14 src="" index="1" />
<AngleIndicator src="" index="1" />
<ArticleVariant src="" index="1" />
<ClippingPathName src="" index="1" />
<ColorMode src="" index="1" readonly="1" />
<Copyright src="" index="1" />
<CreateDate src="" index="1" readonly="1" type="date" />
<Description src="XMP-dc:Description" index="1" />
<ExpirationDate src="XMP-plus:LicenseEndDate" index="1" type="date" />
<FacingIndicator src="" index="1" />
<FileType src="" index="1" />
<Filename src="Phraseanet:tf-basename" index="1" readonly="1" thumbtitle="1"/>
<ImageQADate src="" index="1" readonly="1" type="date" />
<ClippingPathPresent src="" index="1" />
<LegalOwner src="XMP-plus:CopyrightOwnerName" index="1" />
<LegalOwnerContactInfo src="" index="1" />
<MaxAvailHeight src="Phraseanet:tf-height" index="1" readonly="1" type="number" />
<MaxAvailWidth src="Phraseanet:tf-width" index="1" readonly="1" type="number" />
<NumberOfTheImage src="" index="1" />
<PackagingType src="" index="1" />
<ProductNetContent src="" index="1" />
<ProductSupplier src="" index="1" />
<ProductURL src="" index="1" readonly="1" />
<RightOfUse src="XMP-xmpRights:UsageTerms" index="1" multi="1" separator=";" />
<SpecialRights src="" index="1" multi="1" separator=";" />
<VersionNumber src="" index="1" />
<InOutPackaging src="" index="1" />
<ArchiveDate src="Phraseanet:tf-archivedate" readonly="1" type="date" />
<LastEditDate src="Phraseanet:tf-editdate" readonly="1" type="date" />
</description>
<statbits>
<bit n="4" labelOn="Caption_filled" searchable="0" printable="0" labelOff="Unspecified_caption">
<label switch="off" code="en">Caption not filled</label>
<label switch="off" code="fr">Media non renseigne</label>
<label switch="on" code="en">Caption filled</label>
<label switch="on" code="fr">Média renseigné</label>
</bit>
<bit n="5" labelOn="Clipping_Path_Present" searchable="1" printable="0" labelOff="No_Clipping_Path">
<label switch="off" code="en">No Clipping Path</label>
<label switch="off" code="fr">Sans masque de détourage</label>
<label switch="on" code="en">Clipping Path Present</label>
<label switch="on" code="fr">Avec masque de détourage</label>
</bit>
</statbits>
</record>

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.214-d",
"phraseanet-production-client": "0.34.256-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-05-29T13:54:14Z" source-language="en" target-language="de" datatype="plaintext" original="not.available">
<file date="2020-07-08T13:05:50Z" 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-05-29T13:54:38Z" source-language="en" target-language="en" datatype="plaintext" original="not.available">
<file date="2020-07-08T13:06:14Z" 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-05-29T13:55:04Z" source-language="en" target-language="fr" datatype="plaintext" original="not.available">
<file date="2020-07-08T13:06:42Z" 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-05-29T13:55:35Z" source-language="en" target-language="nl" datatype="plaintext" original="not.available">
<file date="2020-07-08T13:07:11Z" 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

@@ -0,0 +1,345 @@
<?php
namespace Alchemy\Phrasea\Model\Proxies\__CG__\Alchemy\Phrasea\Model\Entities;
/**
* DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR
*/
class WorkerRunningJob extends \Alchemy\Phrasea\Model\Entities\WorkerRunningJob implements \Doctrine\ORM\Proxy\Proxy
{
/**
* @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with
* three parameters, being respectively the proxy object to be initialized, the method that triggered the
* initialization process and an array of ordered parameters that were passed to that method.
*
* @see \Doctrine\Common\Persistence\Proxy::__setInitializer
*/
public $__initializer__;
/**
* @var \Closure the callback responsible of loading properties that need to be copied in the cloned object
*
* @see \Doctrine\Common\Persistence\Proxy::__setCloner
*/
public $__cloner__;
/**
* @var boolean flag indicating if this object was already initialized
*
* @see \Doctrine\Common\Persistence\Proxy::__isInitialized
*/
public $__isInitialized__ = false;
/**
* @var array properties to be lazy loaded, with keys being the property
* names and values being their default values
*
* @see \Doctrine\Common\Persistence\Proxy::__getLazyProperties
*/
public static $lazyPropertiesDefaults = [];
/**
* @param \Closure $initializer
* @param \Closure $cloner
*/
public function __construct($initializer = null, $cloner = null)
{
$this->__initializer__ = $initializer;
$this->__cloner__ = $cloner;
}
/**
*
* @return array
*/
public function __sleep()
{
if ($this->__isInitialized__) {
return ['__isInitialized__', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'id', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'databoxId', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'recordId', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'work', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'workOn', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'created', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'published', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'status'];
}
return ['__isInitialized__', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'id', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'databoxId', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'recordId', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'work', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'workOn', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'created', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'published', '' . "\0" . 'Alchemy\\Phrasea\\Model\\Entities\\WorkerRunningJob' . "\0" . 'status'];
}
/**
*
*/
public function __wakeup()
{
if ( ! $this->__isInitialized__) {
$this->__initializer__ = function (WorkerRunningJob $proxy) {
$proxy->__setInitializer(null);
$proxy->__setCloner(null);
$existingProperties = get_object_vars($proxy);
foreach ($proxy->__getLazyProperties() as $property => $defaultValue) {
if ( ! array_key_exists($property, $existingProperties)) {
$proxy->$property = $defaultValue;
}
}
};
}
}
/**
*
*/
public function __clone()
{
$this->__cloner__ && $this->__cloner__->__invoke($this, '__clone', []);
}
/**
* Forces initialization of the proxy
*/
public function __load()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, '__load', []);
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
public function __isInitialized()
{
return $this->__isInitialized__;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
public function __setInitialized($initialized)
{
$this->__isInitialized__ = $initialized;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
public function __setInitializer(\Closure $initializer = null)
{
$this->__initializer__ = $initializer;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
public function __getInitializer()
{
return $this->__initializer__;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
public function __setCloner(\Closure $cloner = null)
{
$this->__cloner__ = $cloner;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific cloning logic
*/
public function __getCloner()
{
return $this->__cloner__;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
* @static
*/
public function __getLazyProperties()
{
return self::$lazyPropertiesDefaults;
}
/**
* {@inheritDoc}
*/
public function getId()
{
if ($this->__isInitialized__ === false) {
return (int) parent::getId();
}
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', []);
return parent::getId();
}
/**
* {@inheritDoc}
*/
public function setDataboxId($databoxId)
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'setDataboxId', [$databoxId]);
return parent::setDataboxId($databoxId);
}
/**
* {@inheritDoc}
*/
public function getDataboxId()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getDataboxId', []);
return parent::getDataboxId();
}
/**
* {@inheritDoc}
*/
public function setRecordId($recordId)
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'setRecordId', [$recordId]);
return parent::setRecordId($recordId);
}
/**
* {@inheritDoc}
*/
public function getRecordId()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getRecordId', []);
return parent::getRecordId();
}
/**
* {@inheritDoc}
*/
public function setWork($work)
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'setWork', [$work]);
return parent::setWork($work);
}
/**
* {@inheritDoc}
*/
public function getWork()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getWork', []);
return parent::getWork();
}
/**
* {@inheritDoc}
*/
public function setWorkOn($workOn)
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'setWorkOn', [$workOn]);
return parent::setWorkOn($workOn);
}
/**
* {@inheritDoc}
*/
public function getWorkOn()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getWorkOn', []);
return parent::getWorkOn();
}
/**
* {@inheritDoc}
*/
public function getCreated()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getCreated', []);
return parent::getCreated();
}
/**
* {@inheritDoc}
*/
public function setPublished(\DateTime $published)
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'setPublished', [$published]);
return parent::setPublished($published);
}
/**
* {@inheritDoc}
*/
public function getPublished()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getPublished', []);
return parent::getPublished();
}
/**
* {@inheritDoc}
*/
public function setStatus($status)
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'setStatus', [$status]);
return parent::setStatus($status);
}
/**
* {@inheritDoc}
*/
public function getStatus()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getStatus', []);
return parent::getStatus();
}
/**
* {@inheritDoc}
*/
public function getWorkName()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getWorkName', []);
return parent::getWorkName();
}
}

View File

@@ -164,3 +164,20 @@ $mainMenuLinkBackgroundHoverColor: transparent;
}
.acceptDl-info {
background-color: #d9edf7;
border: 1px solid #bce8f1;
border-radius: 3px;
color: #3a87ad;
display: inline-block;
padding: 9px 24px 5px 9px;
margin-bottom: 20px;
text-shadow: 0 1px 0 rgba(255,255,255,.5);
a, label {
color: #3a87ad!important;
}
a {
text-decoration: underline;
}
}

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-left" class="svg-inline--fa fa-chevron-left fa-w-10" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path fill="currentColor" d="M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z"></path></svg>

After

Width:  |  Height:  |  Size: 482 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-right" class="svg-inline--fa fa-chevron-right fa-w-10" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path fill="currentColor" d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"></path></svg>

After

Width:  |  Height:  |  Size: 527 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="file-download" class="svg-inline--fa fa-file-download fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="#2196f3" d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm76.45 211.36l-96.42 95.7c-6.65 6.61-17.39 6.61-24.04 0l-96.42-95.7C73.42 337.29 80.54 320 94.82 320H160v-80c0-8.84 7.16-16 16-16h32c8.84 0 16 7.16 16 16v80h65.18c14.28 0 21.4 17.29 11.27 27.36zM377 105L279.1 7c-4.5-4.5-10.6-7-17-7H256v128h128v-6.1c0-6.3-2.5-12.4-7-16.9z"></path></svg>

After

Width:  |  Height:  |  Size: 625 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="file-download" class="svg-inline--fa fa-file-download fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="#ffffff" d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm76.45 211.36l-96.42 95.7c-6.65 6.61-17.39 6.61-24.04 0l-96.42-95.7C73.42 337.29 80.54 320 94.82 320H160v-80c0-8.84 7.16-16 16-16h32c8.84 0 16 7.16 16 16v80h65.18c14.28 0 21.4 17.29 11.27 27.36zM377 105L279.1 7c-4.5-4.5-10.6-7-17-7H256v128h128v-6.1c0-6.3-2.5-12.4-7-16.9z"></path></svg>

After

Width:  |  Height:  |  Size: 625 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="home" class="svg-inline--fa fa-home fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="#ffffff" d="M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z"></path></svg>

After

Width:  |  Height:  |  Size: 708 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="pause" class="svg-inline--fa fa-pause fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M144 479H48c-26.5 0-48-21.5-48-48V79c0-26.5 21.5-48 48-48h96c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48zm304-48V79c0-26.5-21.5-48-48-48h-96c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48z"></path></svg>

After

Width:  |  Height:  |  Size: 444 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="play" class="svg-inline--fa fa-play fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"></path></svg>

After

Width:  |  Height:  |  Size: 339 B

View File

@@ -99,7 +99,7 @@ img {
overflow: scroll;
}
.display_id {
top: 5px;
top: 0;
margin: 0 0 0 5px;
}
}
@@ -134,8 +134,8 @@ img {
background-color: #212121;
}
.display_id {
top: 4px;
left: 8px;
top: 5px;
left: 5px;
}
.agreement {
position: absolute;
@@ -173,7 +173,9 @@ img {
.display_id {
background-color: #FFFFFF;
padding: 3px 6px;
padding: 3.5px 6px;
min-width: 15px;
text-align: center;
font-weight: bold;
z-index: 99;
color: #212121;
@@ -214,6 +216,28 @@ img {
overflow: hidden;
background-color: #1F1E1B;
color: #BFBFBF;
.PNB.choices {
position: relative;
}
.comment_button {
text-align: center;
background: #353430;
margin: 0;
width: 118px;
position: absolute;
left: 50%;
transform: translateX(-50%);
padding: 7px;
font-size: 14px;
line-height: 14px;
border: 0;
bottom: -30px;
.fa-comment.icon-white {
font-size: 15px;
line-height: 18px;
margin-right: 10px;
}
}
}
#basket_options {
@@ -230,6 +254,11 @@ img {
#basket_infos .user_infos {
height: 120px;
top: auto;
bottom: 43px;
}
.lightbox-icon {
width: 10px;
padding-bottom: 1px;
}
#basket_infos {
@@ -284,7 +313,7 @@ img {
}
#right_column .right_column_wrapper {
//top: 30px;
top: 30px;
bottom: 45px;
}
@@ -296,6 +325,7 @@ img {
#record_infos {
overflow-x: hidden;
overflow-y: auto;
margin-bottom: 80px;
}
#record_compare {
@@ -303,7 +333,10 @@ img {
top: auto;
left: auto;
}
.download-feed{
color: #fff;
font-size: 22px;
}
#record_compare .header, #record_compare .lightbox_container {
left: 5px;
}
@@ -546,10 +579,14 @@ table th i {
}
.basket_downloader {
background-color: #1F1E1B;
background-color: #ffffff;
margin: 0 10px;
padding: 2px;
padding: 4px 0;
border: 0;
transition: 0.3s all;
&:hover {
background-color: #e6e6e6;
}
}
hr {
@@ -562,6 +599,13 @@ hr {
.report {
margin: 0 10px;
vertical-align: bottom;
}.back-home {
margin: 0 10px;
vertical-align: bottom;
width: 20px;
background: #1F1E1B;
padding: 5px;
border-radius: 3px;
}
/*
@@ -669,3 +713,25 @@ a.btn-info {
}
/** End Lightbox summary modal**/
.humane{
position: fixed;
z-index: 100000;
font-family: Ubuntu, Arial, sans-serif;
text-align: center;
font-size: 15px;
top: 100px;
right: 70px;
width: 400px;
background: #2d2d72;
color: rgb(255, 255, 255);
box-shadow: rgb(0, 0, 0) 0px 4px 4px -4px;
transform: translateY(-40px);
transition: all 0.3s ease-out 0s;
padding: 10px;
border-radius: 5px;
}
.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text {
padding: 0!important;
}

View File

@@ -1,3 +1,40 @@
$blue-tsr: #2196f3;
$grey-tsr: #aaa;
$iconsPath: '../../../assets/thesaurus/images/';
@mixin blue-btn {
color: #fff;
background: $blue-tsr;
padding: 5px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
border-color: darken($blue-tsr, 20%);
min-width: 120px;
margin: 0 10px;
position: relative;
z-index: 2;
&:hover {
background: darken($blue-tsr, 20%);
}
}
@mixin grey-btn {
color: #000;
background: $grey-tsr;
padding: 5px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
border-color: darken($grey-tsr, 20%);
min-width: 120px;
margin: 0 10px;
position: relative;
z-index: 2;
&:hover {
background: darken($grey-tsr, 20%);
}
}
BODY, TD, INPUT {
FONT-FAMILY: Verdana, Arial, helvetica, sans-serif;
font-size: 12px;
@@ -8,11 +45,11 @@ FORM {
margin: 0px;
}
BODY.dialog {
.dialog {
background-color: ButtonFace;
}
BODY.dialog .x3Dbox {
.x3Dbox {
position: relative;
top: 0px;
left: 0px;
@@ -24,7 +61,7 @@ BODY.dialog .x3Dbox {
border-right: 1px solid #ffffff;
}
BODY.dialog .x3Dbox .title {
.x3Dbox .title {
position: absolute;
top: -9px;
left: 20px;
@@ -58,7 +95,7 @@ H3, H4 {
display: inline;
}
DIV.thbox {
.thbox {
BACKGROUND-COLOR: #ffffff;
border-top: 1px solid #d0d0d0;
border-left: 1px solid #d0d0d0;
@@ -69,7 +106,7 @@ DIV.thbox {
z-index: 1;
}
DIV.thbox .onglet {
.thbox .onglet {
position: absolute;
top: -22px;
left: 5px;
@@ -87,20 +124,20 @@ DIV.thbox .onglet {
z-index: 2;
}
DIV.thbox HR {
.thbox HR {
COLOR: #d0d0d0;
height: 1px;
margin: 0px;
padding: 0px;
}
DIV.glossaire {
.glossaire {
position: relative;
left: 4px;
white-space: nowrap;
}
DIV.glossaire U {
.glossaire U {
position: relative;
left: 1px;
TEXT-DECORATION: none;
@@ -116,30 +153,30 @@ DIV.glossaire U {
cursor: pointer;
}
DIV.glossaire U.nots {
.glossaire U.nots {
border: 1px solid #F0F0F0;
BACKGROUND-COLOR: #F0F0F0;
cursor: default;
}
DIV.glossaire DIV.S_ {
.glossaire .S_ {
cursor: pointer;
BACKGROUND-COLOR: #99a2d0;
COLOR: #FFFFFF;
}
DIV.glossaire DIV.s_ {
.glossaire .s_ {
cursor: pointer;
}
DIV.glossaire DIV.r0_ {
.glossaire .r0_ {
}
DIV.glossaire DIV.r1_ {
.glossaire .r1_ {
COLOR: #FF4000;
}
DIV.glossaire DIV.OB {
.glossaire .OB {
position: relative;
top: 0px;
padding-top: 0px;
@@ -149,11 +186,11 @@ DIV.glossaire DIV.OB {
border-bottom: 1px solid #cccccc;
}
DIV.glossaire DIV.ob {
.glossaire .ob {
display: none;
}
DIV.glossaire DIV.hb {
.glossaire .hb {
position: relative;
top: 0px;
padding-top: 0px;
@@ -163,46 +200,46 @@ DIV.glossaire DIV.hb {
border-bottom: 1px solid #cccccc;
}
DIV.glossaire DIV.ctroot {
.glossaire .ctroot {
position: relative;
top: 0px;
padding: 0px;
border: none;
}
DIV.glossaire P.sy {
.glossaire P.sy {
}
DIV.glossaire P.ta {
.glossaire P.ta {
}
DIV.glossaire P.tc {
.glossaire P.tc {
}
DIV.glossaire P.tce {
.glossaire P.tce {
}
DIV.tableContainer {
.tableContainer {
margin: 2px;
border: 1px solid #000000;
background-color: #ffffff;
}
DIV.tableContainerDragOver {
.tableContainerDragOver {
margin: 0px;
border: 3px solid #99a2d0;
}
DIV.tableContainer DIV.tbody {
.tableContainer .tbody {
position: relative;
overflow: auto;
}
DIV.tableContainer TABLE {
.tableContainer TABLE {
table-layout: fixed;
}
DIV.tableContainer THEAD TH {
.tableContainer THEAD TH {
background: #e0ece8;
border-left: 1px solid #ffffff;
border-right: 1px solid #c0ccc8;
@@ -216,15 +253,15 @@ DIV.tableContainer THEAD TH {
margin: 0px;
}
DIV.tableContainer TBODY TR.s_ {
.tableContainer TBODY TR.s_ {
}
DIV.tableContainer TBODY TR.S_ {
.tableContainer TBODY TR.S_ {
BACKGROUND-COLOR: #99a2d0;
COLOR: #FFFFFF;
}
DIV.tableContainer TBODY TD {
.tableContainer TBODY TD {
border-left: 1px solid #ffffff;
border-right: 1px solid #cccccc;
font-weight: normal;
@@ -235,77 +272,68 @@ DIV.tableContainer TBODY TD {
margin: 0px;
}
DIV.menu {
.menu {
FONT-FAMILY: Arial, helvetica, sans-serif;
font-size: 12px;
border-left: 1px solid #ffffff;
border-top: 1px solid #ffffff;
border-right: 2px solid #000000;
border-bottom: 2px solid #000000;
padding: 0px;
margin: 0px;
visibility: hidden;
position: absolute;
top: 0px;
left: 0px;
background-color: #d4d0c8;
top: 0;
left: 0;
background-color: #535353;
}
DIV.menu IMG {
padding: 0px;
margin: 0px;
.menu IMG {
padding-right: 2px;
margin: 0;
position: relative;
left: -10px;
top: 2px;
}
DIV.menu A {
.menu p {
padding: 2px;
position: relative;
}
.menu a {
font-size: 12px;
display: block;
position: relative;
text-decoration: none;
color: #000000;
padding-top: 1px;
padding-bottom: 1px;
padding-left: 13px;
padding-right: 3px;
overflow: hidden;
border: none 0px #FFFFFF;
white-space: nowrap;
color: #fff;
padding: 5px 6px;
background-color: #535353;
}
DIV.menu A:hover {
.menu A:hover {
font-size: 12px;
display: block;
position: relative;
text-decoration: none;
color: #ffffff;
background-color: #000080;
background-color: #884c92;
}
DIV.menu A.disabled {
.menu A.disabled {
font-size: 12px;
display: block;
position: relative;
text-decoration: none;
color: #A0A0A0;
padding-top: 1px;
padding-bottom: 1px;
padding-left: 13px;
padding-right: 3px;
color: lighten(#535353, 10%);
overflow: hidden;
}
DIV.menu A.disabled:hover {
.menu A.disabled:hover {
font-size: 12px;
display: block;
position: relative;
text-decoration: none;
color: #A0A0A0;
background-color: #d4d0c8;
color: lighten(#535353, 5%);
background-color: #535353;
}
DIV.menu .line {
.menu .line {
display: block;
position: relative;
height: 0px;
@@ -316,3 +344,90 @@ DIV.menu .line {
border-top: 1px solid #555555;
border-bottom: 1px solid #ffffff;
}
#delete_sy {
background: orange;
border: 1px solid darken(orange,20%);
color: #fff;
font-weight: bold;
padding: 1px 10px;
margin-left: 12px;
&.disabled {
background: grey;
}
}
#DLG_PROPERTIES > #syMenu {
display: none;
}
.thesaurus_confirm_bottom_block {
margin-top: 20px;
text-align: center;
#ifrsample & {
display: none;
}
}
.thesaurus-page {
.validate_btn {
@include blue-btn;
}
.cancel_btn {
@include grey-btn;
}
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
float: none;
}
.ui-dialog-buttonset {
text-align: center;
}
.ui-button {
&:nth-child(1) {
@include grey-btn
}
&:nth-child(2) {
@include blue-btn
}
}
}
.text-center {
text-align: center;
padding-bottom: 10px;
}
.populate_btn {
position: absolute;
background: #884c92;
z-index: 2;
right: 50px;
top: 50px;
font-size: 14px;
color: #fff;
padding: 7px 10px;
font-weight: bold;
border: 1px solid #d0d0d0;
cursor: pointer;
&:hover {
background: darken(#884c92, 20%);
}
}
.thesaurus-page {
.ui-widget-header {
border: 1px solid darken(#884c92, 20%);
background: #884c92;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
}
#flagsMenu p {
color: #fff;
padding: 0 10px;
}

View File

@@ -35,6 +35,11 @@
{{ 'admin::workermanager:tab:metadata: title' |trans }}
</a>
</li>
<li class="worker-queue-monitor" role="presentation">
<a href="#worker-queue-monitor" aria-controls="worker-queue-monitor" role="tab" data-toggle="tab" data-url="/admin/worker-manager/queue-monitor">
{{ 'admin::workermanager:tab:queueMonitor: title' |trans }}
</a>
</li>
</ul>
@@ -48,6 +53,7 @@
<div role="tabpanel" class="tab-pane fade" id="worker-pull-assets"></div>
<div role="tabpanel" class="tab-pane fade" id="worker-subview"></div>
<div role="tabpanel" class="tab-pane fade" id="worker-metadata"></div>
<div role="tabpanel" class="tab-pane fade" id="worker-queue-monitor"></div>
</div>
</div>

View File

@@ -42,7 +42,7 @@
<table class="admintable">
<thead>
<tr>
<th class="sortable">{{ 'admin::workermanager:tab:workerinfo: databox_id' | trans }}</th>
<th class="sortable">{{ 'admin::workermanager:tab:workerinfo: databox_name' | trans }}</th>
<th class="sortable">{{ 'admin::workermanager:tab:workerinfo: record_id' | trans }}</th>
<th class="sortable">{{ 'admin::workermanager:tab:workerinfo: work' | trans }}</th>
<th class="sortable">{{ 'admin::workermanager:tab:workerinfo: work_on' | trans }}</th>
@@ -57,7 +57,7 @@
{% for workerRow in workerRunningJob | sort | reverse %}
<tr>
<td>{{ workerRow.databoxId }}</td>
<td>{{ workerRow.databoxId | sbas_labels(app) }}</td>
<td>{{ workerRow.recordId }}</td>
<td>{{ workerRow.getWorkName }}</td>
<td>{{ workerRow.workOn }}</td>

View File

@@ -0,0 +1,42 @@
{% if not reload %}
<h1>{{ 'admin::workermanager:tab:queueMonitor: description' |trans }}</h1>
<button id="refresh-monitor" class="btn btn-success">
{{ 'admin::workermanager:tab:queueMonitor: Refresh list' |trans }}
</button>
<table class="admintable">
<thead>
<tr>
<th></th>
<th>{{ 'admin::workermanager:tab:queueMonitor: Message count' |trans }}</th>
<th>{{ 'admin::workermanager:tab:queueMonitor: Consumer count' |trans }}</th>
</tr>
</thead>
<tbody class="queue-list">
{% endif %}
{% for queueStatus in queuesStatus %}
<tr>
<th>{{ queueStatus.queueName }}</th>
<td>{{ queueStatus.messageCount }}</td>
<td>{{ queueStatus.consumerCount }}</td>
</tr>
{% endfor %}
{% if not reload %}
</tbody>
</table>
<script type="text/javascript">
$("#refresh-monitor").on('click', function () {
$.ajax({
type: "GET",
url: "/admin/worker-manager/queue-monitor?reload=1",
success: function (data) {
$(".queue-list").empty().html(data);
}
});
});
</script>
{% endif %}

View File

@@ -152,9 +152,8 @@
</div>
{% endif %}
{{ _self.choose_title('download', choose_export_title, default_export_title) }}
{% if app['conf'].get(['registry', 'actions', 'tou-validation-required-for-export']) == true %}
<div class="well-small">
<div class="well-small acceptDl-info">
<label for="TOU_acceptDL" class="checkbox">
<input type="checkbox" name="TOU_accept" id="TOU_acceptDL" value="1" />
{% set beginning_link = '<a href="' ~ path('get_tou') ~ '" class="TOUview">' %}
@@ -177,21 +176,27 @@
<input type="hidden" name="lst" value="{{lst}}"/>
<input type="hidden" name="ssttid" value="{{ssttid}}"/>
<div>
{{ 'export::mail: destinataire' | trans }}
<span style="min-width: 21px;display: inline-block; ">
{{ 'export::mail: destinataire' | trans }}</span>
<input type="text" value="" name="destmail" class="required span4">
<div class="acceptDl-info" style="padding-top: 4px; margin-left: 25px;">
<span style="font-style: italic">{{ 'Entrez plusieurs adresses email en les separant par des points-virgules' | trans }}</span>
</div>
{% set my_email = app.getAuthenticatedUser().getEmail() %}
{% if my_email != '' %}
<label class="checkbox">
<input type="checkbox" name="reading_confirm" value="1" />
{% trans with {'%my_email%' : my_email} %}Recevoir un accuse de reception a %my_email%{% endtrans %}
</label>
{% else %}
<label class="checkbox">
{{ 'Accuse de reception indisponible, vous n\'avez pas declare d\'adresse email' | trans }}
<input type="checkbox" name="reading_confirm" value="1" readonly />
</label>
{% endif %}
<span style="font-style: italic; color: #999999;">{{ 'Entrez plusieurs adresses email en les separant par des points-virgules' | trans }}</span>
<div class="acceptDl-info">
{% if my_email != '' %}
<label class="checkbox">
<input type="checkbox" name="reading_confirm" value="1" />
{% trans with {'%my_email%' : my_email} %}Recevoir un accuse de reception a %my_email%{% endtrans %}
</label>
{% else %}
<label class="checkbox">
{{ 'Accuse de reception indisponible, vous n\'avez pas declare d\'adresse email' | trans }}
<input type="checkbox" name="reading_confirm" value="1" readonly />
</label>
{% endif %}
<span style="font-style: italic; color: #999999;">{{ 'export:email:: acknowledgement info' | trans }}</span>
</div>
</div>
<div>
<p>{{ 'export::mail: contenu du mail' | trans }}</p>
@@ -235,7 +240,7 @@
{{ _self.choose_title('sendmail', choose_export_title, default_export_title) }}
{% if app['conf'].get(['registry', 'actions', 'tou-validation-required-for-export']) == true %}
<div class="well-small">
<div class="well-small acceptDl-info">
<label for="TOU_acceptMail" class="checkbox">
<input type="checkbox" name="TOU_accept" id="TOU_acceptMail" value="1" />
{% set beginning_link = '<a href="' ~ path('get_tou') ~ '" class="TOUview">' %}
@@ -388,7 +393,7 @@
</div>
{% if app['conf'].get(['registry', 'actions', 'tou-validation-required-for-export']) == true %}
<div class="well-small">
<div class="well-small acceptDl-info">
<label for="TOU_acceptOrder" class="checkbox">
<input type="checkbox" name="TOU_accept" id="TOU_acceptOrder" value="1" />
{% set beginning_link = '<a href="' ~ path('get_tou') ~ '" class="TOUview">' %}
@@ -469,7 +474,7 @@
{% endif %}
{% if app['conf'].get(['registry', 'actions', 'tou-validation-required-for-export']) == true %}
<div class="well-small">
<div class="well-small acceptDl-info">
<label for="TOU_acceptFTP" class="checkbox">
<input type="checkbox" name="TOU_accept" id="TOU_acceptFTP" value="1" />
{% set beginning_link = '<a href="' ~ path('get_tou') ~ '" class="TOUview">' %}

View File

@@ -232,7 +232,7 @@
{% endif %}
</li>
<li>
<a target="_blank" href="https://docs.phraseanet.com/4.0/">
<a target="_blank" href="{{ 'https://docs.phraseanet.com/4.1/' ~ app['locale'] }}">
<span>
{{ 'phraseanet:: aide' | trans }}
</span>

View File

@@ -47,4 +47,16 @@
</div>
</div>
{% endif %}
{% if basket_element.getBasket().getValidation() %}
<button class="comment_button btn btn-mini btn-inverse">
<i class="fa fa-comment icon-white" aria-hidden="true"></i>
{% set n = 0 %}
{% for validationData in basket_element.getValidationDatas() %}
{% if validationData.getNote() %}
{% set n = n+1 %}
{% endif %}
{% endfor %}
{{ n }}
</button>
{% endif %}
</div>

View File

@@ -92,11 +92,12 @@
<div class="PNB" style="height:30px;bottom:auto;">
<table border="0" cellspacing="0" cellpadding="0" style="width:100%;">
<tr>
<td style="width:20px;">
<td style="width:50px;">
<a href="/lightbox"><img title="{{ 'lightbox::recaptitulatif' | trans }}" class="back-home" src="/assets/lightbox/images/home-solid.svg"/></a>
</td>
<td style="width:40px;">
<button class="ui-corner-all basket_downloader" title="{{ 'boutton::telecharger tous les documents' | trans }}">
<img src="/assets/lightbox/images/save.png"/>
<img width="15" src="/assets/lightbox/images/file-download-solid-blue.svg" >
</button>
</td>
<td>

View File

@@ -1,25 +1,25 @@
<span style="padding:10px 4px 7px;">
{% if feed_element %}
<button class="previous_button play btn btn-mini" title="{{ 'boutton::precedent' | trans }}">
<i class="fa fa-chevron-left" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/chevron-left-solid.svg"/>
</button>
<button class="previous_button pause btn btn-mini btn-inverse" title="{{ 'boutton::precedent' | trans }}">
<i class="fa fa-chevron-left icon-white" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/chevron-left-solid.svg"/>
</button>
<button class="play_button btn btn-mini" title="{{ 'boutton::demarrer' | trans }}">
<i class="fa fa-play" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/play-solid.svg"/>
</button>
<button class="pause_button btn btn-mini" title="{{ 'boutton::pause' | trans }}">
<i class="fa fa-pause" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/pause-solid.svg"/>
</button>
<button class="next_button play btn btn-mini" title="{{ 'boutton::suivant' | trans }}">
<i class="fa fa-chevron-right" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/chevron-right-solid.svg"/>
</button>
<button class="next_button pause btn btn-mini btn-inverse" title="{{ 'boutton::suivant' | trans }}">
<i class="fa fa-chevron-right icon-white" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/chevron-right-solid.svg"/>
</button>
<button class="download_button btn btn-mini btn-inverse" title="{{ 'boutton::telecharger' | trans }}">
<i class="fa fa-arrow-circle-o-down icon-white" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/file-download-solid.svg"/>
</button>
<form name="download_form" style="display:none;">
<input type="hidden" name="basrec" value="{{feed_element.getRecord(app).get_serialize_key()}}"/>

View File

@@ -11,10 +11,6 @@
<link type="text/css" rel="stylesheet" href="/assets/lightbox/css/lightbox{% if not app.debug %}.min{% endif %}.css" media="screen"/>
{% endblock %}
{% block icon %}
<link rel="shortcut icon" type="image/x-icon" href="/assets/lightbox/images/favicon.ico">
{% endblock %}
{% block content %}
<div id="main_index" style="margin-top:50px;">
<table id="main_wrapper" cellspacing="0" cellpadding="0">

View File

@@ -1,5 +1,8 @@
{% extends "common/index_bootstrap.html.twig" %}
{% block icon %}
<link rel="shortcut icon" type="image/x-icon" href="/assets/lightbox/images/favicon.ico">
{% endblock %}
{% block extra_content %}
<div id="DIALOG"></div>
{% endblock %}

View File

@@ -1,42 +1,31 @@
<span style="padding:10px 4px 7px;">
{% if basket_element %}
<button class="previous_button play btn btn-mini" title="{{ 'boutton::precedent' | trans }}">
<i class="fa fa-chevron-left" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/chevron-left-solid.svg"/>
</button>
<button class="previous_button pause btn btn-mini btn-inverse" title="{{ 'boutton::precedent' | trans }}" style="display:none">
<i class="fa fa-chevron-left icon-white" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/chevron-left-solid.svg"/>
</button>
<button class="play_button btn btn-mini" title="{{ 'boutton::demarrer' | trans }}">
<i class="fa fa-play" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/play-solid.svg"/>
</button>
<button class="pause_button btn btn-mini" title="{{ 'boutton::pause' | trans }}" style="display:none">
<i class="fa fa-pause" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/pause-solid.svg"/>
</button>
<button class="next_button play btn btn-mini" title="{{ 'boutton::suivant' | trans }}">
<i class="fa fa-chevron-right" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/chevron-right-solid.svg"/>
</button>
<button class="next_button pause btn btn-mini btn-inverse" title="{{ 'boutton::suivant' | trans }}" style="display:none">
<i class="fa fa-chevron-right icon-white" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/chevron-right-solid.svg"/>
</button>
<button class="download_button btn btn-mini btn-inverse" title="{{ 'boutton::telecharger' | trans }}">
<i class="fa fa-arrow-circle-o-down icon-white" aria-hidden="true"></i>
<img class="lightbox-icon" src="/assets/lightbox/images/file-download-solid.svg"/>
</button>
<form name="download_form" style="display:none;">
<input type="hidden" name="basrec" value="{{basket_element.getRecord(app).get_serialize_key()}}"/>
</form>
|
{% if basket_element.getBasket().getValidation() %}
<button class="comment_button btn btn-mini btn-inverse">
<i class="fa fa-pencil-square-o icon-white" aria-hidden="true"></i>
{% set n = 0 %}
{% for validationData in basket_element.getValidationDatas() %}
{% if validationData.getNote() %}
{% set n = n+1 %}
{% endif %}
{% endfor %}
{{ n }}
</button>
{% endif %}
{% endif %}
</span>

View File

@@ -15,6 +15,7 @@
{% block content %}
{% set basket_element = basket.getElements().first() %}
<div class="PNB10">
<input type="hidden" id="export-send-mail-notif" value="{{ 'prod::export: send mail notification' | trans }} ">
<div class="PNB" id="top_container">
<div id="record_wrapper" class="PNB single" style="right:250px;">
<div id="record_main" class="PNB record_display_box" style="bottom:auto;right:auto;">
@@ -155,11 +156,10 @@
<tr>
<td style="width:20px;">
</td>
{% if basket.getValidation() %}
<td style="width:50px;">
<img title="{{ 'lightbox::recaptitulatif' | trans }}" class="report" src="/assets/lightbox/images/retour.png"/>
</td>
{% endif %}
<td style="width:50px;">
<a href="/lightbox"><img title="{{ 'lightbox::recaptitulatif' | trans }}" class="back-home" src="/assets/lightbox/images/home-solid.svg"/></a>
</td>
<td style="width:220px;">
<select id="navigation" style='margin:0'>
<optgroup label="{{ 'Validations' | trans }}">
@@ -182,7 +182,7 @@
</td>
<td style="width:50px;">
<button style='width:30px;margin:0;' class="ui-corner-all basket_downloader" title="{{ 'boutton::telecharger tous les documents' | trans }}">
<img src="/assets/lightbox/images/save.png"/>
<img width="15" src="/assets/lightbox/images/file-download-solid-blue.svg" >
</button>
</td>
<td>

View File

@@ -1,6 +1,6 @@
<div class="PNB">
<div class="PNB header" style="height:40px;bottom:auto;">
<table>
<table class="detailed_basket_browser">
<tr>
<td style="width:230px;">
<a href="#" class="back">
@@ -23,9 +23,9 @@
</span>
</a>
{% endif %}
<a class="basket_link" href="#">
{{ Basket.getName() }}
</a>
<span class="basket_link" title="{{ Basket.getName() }}">
{{ Basket.getName()|length > 55 ? Basket.getName()|slice(0, 52) ~ '...' : Basket.getName() }}
</span>
</h1>
</td>
</tr>

View File

@@ -40,8 +40,8 @@
&nbsp;
</a>
{% endif %}
<a class="basket_link" href="{{ path('prod_workzone_basket', { basket : Basket.getId() }) }}">
<span>{{ Basket.getName() }}</span>
<a title="{{ Basket.getName()}}" class="basket_link" href="{{ path('prod_workzone_basket', { basket : Basket.getId() }) }}">
<span> {{ Basket.getName()|length > 80 ? Basket.getName()|slice(0, 77) ~ '...' : Basket.getName() }}</span>
<br><span class="basketCount">
{{ Basket.getElements().count() }} {{ ' records' }}
</span></a>

View File

@@ -29,6 +29,16 @@
{{ "video range extractor" | trans }}
</a>
</li>
<li>
<a href="#subtitleEditor" class="subtitleEditortoggle">
{{ "prod:videoeditor:subtitleTab:: title" | trans }}
</a>
</li>
<li>
<a href="#subtitleRequest" class="subtitleEditortoggle">
{{ "prod:videoeditor:subtitleRequestTab:: title" | trans }}
</a>
</li>
</ul>
</div>
@@ -82,7 +92,9 @@
</video>
</div>
<div class="videotools-spinner ui-widget-overlay ui-front hidden" id="videotools-spinner" style="opacity: 0.9">
<img src="/assets/common/images/icons/loading.svg" alt="" id="gif-loader">
</div>
<div id="thumb_camera_button"></div>
<div class="vertical-divider"></div>
@@ -158,6 +170,108 @@
<div id="rangeExtractor" class="">
<div class="video-range-editor-container"></div>
</div>
<div id="subtitleEditor" class="subtitleEditor video-subtitle-editor-container">
<input type="hidden" id="defaultStartValue" value="00:00:00.000">
<input type="hidden" id="defaultEndValue" value="00:00:02.000">
<div id="default-item" class="default-item hide">
<fieldset class='video-subtitle-item'><span class='number'>0</span>
<div class='item-field start-time' ><label>{{ "prod:videoeditor:subtitletab:: Start time" | trans }}</label><input class='time startTime' type='text' name='startTime' size='12' value="00:00:00.000"/></div>
<div class='item-field end-time'><label>{{ "prod:videoeditor:subtitletab:: End time" | trans }}</label><input class='time endTime' type='text' name='endTime' size='12'value="00:00:02.000" /></div>
<div class='item-field show-for-time'><label>{{ "prod:videoeditor:subtitletab:: Show for" | trans }}</label><input class='showForTime' readonly type='text' size='12' value="00:00:02.000"/></div>
<div class='item-field caption-text'><textarea class="captionText" name='captionText' placeholder='{{ "prod:videoeditor:subtitletab:: Caption placeholder" | trans }}' rows='2' ></textarea></div>
<div class='remove-item'><i class='fa fa-times-circle'></i></div>
</fieldset>
</div>
<form name="video_subtitle_data" id="video-subtitle-data">
<div class="video-subtitle-top">
<label>{{ "prod:videoeditor:subtitletab:: work on" | trans }}</label>
<select name="meta_struct_id" id="metaStructId">
{% for videoTextTrackField in videoTextTrackFields %}
<option value="{{ videoTextTrackField.meta_struct_id}}">{{ videoTextTrackField.label}}</option>
{% endfor %}
</select>
{% for videoTextTrackField in videoTextTrackFields %}
<input type="hidden" id="caption_{{ videoTextTrackField.meta_struct_id}}" value='{{ videoTextTrackField.value}}' name='databox_id'>
{% endfor %}
<input type="text" id="record-vtt">
<input type="hidden" value='{{ record.get_sbas_id() }}' name='databox_id'>
<input type="hidden" value='{{ record.get_record_id() }}' name='record_id'>
<input type="hidden" id="no_caption" value='{{ "prod:videoeditor:subtitletab:: No caption message" | trans }}'>
</div>
</form>
<form name="video_subtitle_list" id="video-subtitle-list">
<div class="video-subtitle-bottom">
<div class="video-subtitle-left">
<div class="video-subtitle-left-inner">
<div class="fields-wrapper">
</div>
</div>
<div class="video-subtitle-left-button">
<button type="button" id="submit-subtitle" class="btn submit-subtitle btn-blue">{{ "prod:videoeditor:subtitletab:: save" | trans }}</button>
<button type="button" id="copy-subtitle" class="btn copy-subtitle btn-blue">{{ "prod:videoeditor:subtitletab:: copy to clipboard" | trans }}</button>
<button class="add-subtitle-vtt" tabindex="0" type="button" aria-label="Add VTT" title = {{ "prod:videoeditor:subtitletab:: add caption" | trans }}>
<i class="fa fa-plus"></i>
</button>
</div>
</div>
<div class="video-subtitle-right" style="overflow: hidden;">
<div class="video-subtitle-wrapper">
</div>
</div>
</div>
</form>
</div>
<div id ="subtitleRequest" class="subtitleRequest">
<div class="video-subtitle-bottom">
<div class="video-subtitle-left">
<div class="video-request-left-inner">
<form id="video-subtitle-request" class="video-subtitle-request">
<p class="item">
<label>{{ "prod:videoeditor:subtitleRequestTab:label:: Provider" | trans }}</label>
<select name="subtitleProvider" id="subtitle_provider">
<option value="Ginger">Ginger</option>
</select>
</p>
<p class="item">
<label>{{ "prod:videoeditor:subtitleRequestTab:label:: Kind" | trans }}</label>
<select name="subtitle_kindr" id="subtitle_kindr">
<option value="autosubtitling">{{ "prod:videoeditor:subtitleRequestTab:: Autosubtitling" | trans }}</option>
</select>
</p>
<p class="item">
<label>{{ "prod:videoeditor:subtitleRequestTab:label:: Source Audio language" | trans }}</label>
<select name="subtitle_language_source" id="subtitle_language_source">
{% for videoTextTrackField in videoTextTrackFields %}
<option value="{{ videoTextTrackField.meta_struct_id}}">{{ videoTextTrackField.label}}</option>
{% endfor %}
</select>
</p>
<p class="item">
<label>{{ "prod:videoeditor:subtitleRequestTab:label:: Language destination" | trans }}</label>
<select name="subtitle_language_destination" id="subtitle_language_destination">
{% for videoTextTrackField in videoTextTrackFields %}
<option value="{{ videoTextTrackField.meta_struct_id}}">{{ videoTextTrackField.label}}</option>
{% endfor %}
</select>
</p>
<div class="video-subtitle-center-button">
<button type="button" id="submit-subtitle-request" class="btn submit-subtitle btn-blue">{{ "prod:videoeditor:subtitleRequestTab:: submit" | trans }}</button>
</div>
<input type="hidden" value='{{ record.get_sbas_id() }}' name='record_sbas_id'>
<input type="hidden" value='{{ record.get_record_id() }}' name='record_record_id'>
<p class="text-center alert-wrapper hide" id="request-status">
<span class="alert alert-info">{{ "prod:videoeditor:subtitleRequestTab:: Request in process" | trans }}</span>
</p>
</form>
</div>
</div>
<div class="video-subtitle-right">
<div class="video-subtitle-wrapper">
</div>
</div>
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
@@ -173,7 +287,14 @@
>
</iframe>
</div>
<style>
.video-subtitle-editor-container .number {
float: none;
}
.video-subtitle-editor-container .editing .number {
float: left;
}
</style>
<script type="text/javascript">
var toolsConfig = {
selectionLength: {{ selectionLength }},
@@ -217,6 +338,8 @@
{% for subdef in previewHtml5 %}
{
ratio: '{{ ratio }}',
width: '{{ width }}',
height: '{{ height }}',
framerate: {{ record.exif[constant('media_subdef::TC_DATA_FRAMERATE')] | round(2) }},
type: "{{ subdef.get_mime() }}",
src: "{{ subdef.get_url() }}"
@@ -231,6 +354,28 @@
preferences: {
overlapChapters: {% if overlapChapters != NULL %}{{ overlapChapters }}{% else %}1{% endif %},
}
};
$('#submit-subtitle-request').on('click', function (e) {
e.preventDefault();
console.log("auto-subtitle process");
$.ajax({
type: 'POST',
url: '/prod/tools/auto-subtitle/',
dataType: 'json',
data: {
databox_id: {{ record.getDataboxId }},
record_id: {{ record.getRecordId }},
subtitle_language_source: $('#subtitle_language_source option:selected').text(),
meta_struct_id_source: $('#subtitle_language_source').val(),
subtitle_language_destination: $('#subtitle_language_destination option:selected').text(),
meta_struct_id_destination: $('#subtitle_language_destination').val()
},
success: function success(data) {
console.log(data);
$('#request-status').removeClass('hide');
}
});
});
</script>

View File

@@ -45,7 +45,7 @@
<label for="feed_add_title"><b>{{ 'publication : titre' | trans }}</b> &nbsp;<span>( {{ 'publication : title warning' | trans }} )</span></label>
<input class="required_text input-block-level" style="max-width:500px" type="text" name="title" id="feed_add_title" value="{{title}}" />
<label><span class="feed_title_warning feed_warning">{{ 'publication : title alert' | trans }}</span></label>
<label for="feed_add_subtitle"><b>{{ 'publication : sous titre' | trans }}</b> &nbsp;<span class="feed_subtitle_warning feed_warning">{{ 'publication : subtitle warning' | trans }}</span></label>
<label for="feed_add_subtitle"><b>{{ 'publication : sous titre' | trans }}</b> &nbsp;<span>( {{ 'publication : subtitle warning' | trans }} )</span></label>
<textarea id="feed_add_subtitle" style="max-width:500px" class="input-block-level" name="subtitle" rows="5">{{desc}}</textarea>
<label><span class="feed_subtitle_warning feed_warning">{{ 'publication : subtitle alert' | trans }}</span></label>
<label for="feed_add_author_name"><b>{{ 'publication : autheur' | trans }}</b></label>

View File

@@ -1015,6 +1015,7 @@
<input type="hidden" id="push-new-list-title" value="{{ 'prod::push: New list title' | trans }} ">
<input type="hidden" id="push-list-name-empty" value="{{ 'prod::push: List name can not be empty' | trans }} ">
<input type="hidden" id="btn-add" value="{{ 'prod::push: add' | trans }} ">
<input type="hidden" id="export-send-mail-notif" value="{{ 'prod::export: send mail notification' | trans }} ">
<script type="text/javascript" id="bitly_loader"></script>
<script type="text/javascript">
$(document).ready(function(){

View File

@@ -22,25 +22,24 @@
<script type="text/javascript" src="/assets/thesaurus/js/thesaurus{% if not app.debug %}.min{% endif %}.js"></script>
{#<script type="text/javascript" src="{{ path('minifier', { 'f' : 'skins/thesaurus/xmlhttp.js' }) }}"></script>#}
<script type="text/javascript">
function loaded()
{
window.name="ACCEPT";
self.focus();
}
//$('.close-dialog').trigger('click');
function ok()
{
as = "";
if((n=document.forms[0].as.length) > 0)
if($(".as_1").length > 0)
{
for(i=0; i<n && as==""; i++)
{
if(document.forms[0].as[i].checked)
as = document.forms[0].as[i].value;
}
if($(".as_1")[0].checked) {
as = $(".as_1").val();
}
if($(".as_2").length > 0 && $(".as_2")[0].checked) {
as = $(".as_2").val();
}
}
else
{
as = document.forms[0].as.value;
if($(".as_3").length > 0) {
as = $(".as_3").val();
}
}
if(as == "TS")
{
@@ -59,14 +58,13 @@
switch(refresh.item(i).getAttribute("type"))
{
case "CT":
{{ opener }}.reloadCtermsBranch(refresh.item(i).getAttribute("id"));
reloadCtermsBranch(refresh.item(i).getAttribute("id"));
break;
case "TH":
{{ opener }}.reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
break;
}
}
self.close();
}
else if(as == "SY")
{
@@ -85,19 +83,23 @@
switch(refresh.item(i).getAttribute("type"))
{
case "CT":
{{ opener }}.reloadCtermsBranch(refresh.item(i).getAttribute("id"));
reloadCtermsBranch(refresh.item(i).getAttribute("id"));
break;
case "TH":
{{ opener }}.reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
break;
}
}
self.close();
}
$('.close-dialog').trigger('click');
}
function closeModal() {
$('.close-dialog').trigger('click');
}
</script>
</head>
<body id="desktop" onload="loaded();" class="dialog">
<body id="desktop" class="dialog">
{% if not cterm_found %}
<center>
@@ -109,11 +111,9 @@
<br/>
{{ 'thesaurus:: refresh' | trans }}
<br/>
<br/>
<br/>
<br/>
<br/>
<input style="position:relative; z-index:2; width:100px" type="button" id="cancel_button" value="{{ 'boutton::fermer' | trans }}" onclick="self.close();">
<div class="thesaurus_confirm_bottom_block">
<input type="button" class="cancel_btn" id="cancel_button" value="{{ 'boutton::fermer' | trans }}" onclick="closeModal();">
</div>
{% else %}
{% if not term_found %}
<center>
@@ -125,11 +125,9 @@
<br/>
{{ 'thesaurus:: refresh' | trans }}
<br/>
<br/>
<br/>
<br/>
<br/>
<input style="position:relative; z-index:2; width:100px" type="button" id="cancel_button" value="{{ 'boutton::fermer' | trans }}" onclick="self.close();">
<div class="thesaurus_confirm_bottom_block">
<input type="button" class="cancel_btn" id="cancel_button" value="{{ 'boutton::fermer' | trans }}" onclick="closeModal();">
</div>
{% else %}
{% if acceptable %}
<center>
@@ -144,9 +142,9 @@
{{ 'thesaurus:: Accepter le terme comme' | trans }}
<br/><br/><h4>{{ fullpath_src | raw }}</h4><br/><br/>
<br/>
<input type='radio' name='as' value='TS' checked>{{ 'thesaurus:: comme terme specifique' | trans }}
<input type='radio' name='as' class="as_1" value='TS' checked>{{ 'thesaurus:: comme terme specifique' | trans }}
<br/><br/>
<input type='radio' name='as' value='SY'>
<input type='radio' name='as' class="as_2" value='SY'>
{% set fullpath_tgt_raw = fullpath_tgt | raw %}
{% trans with {'%fullpath_tgt_raw%' : fullpath_tgt_raw} %}thesaurus:: comme synonyme de %fullpath_tgt_raw%{% endtrans %}
<br/>
@@ -155,13 +153,12 @@
{{ 'thesaurus:: Accepter la branche comme' | trans }}
&nbsp;{{ 'thesaurus:: comme terme specifique' | trans }}
<br/><br/><h4>{{ fullpath_tgt | raw }}</h4><br/><br/>
<input type='hidden' name='as' value='TS'>
<input type='hidden' name='as' class="as_3" value='TS'>
{% endif %}
<br/>
<br/>
<input style="position:relative; z-index:2; width:100px" type="button" id="ok_button" value="{{ 'boutton::valider' | trans }}" onclick="ok();">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<input style="position:relative; z-index:2; width:100px" type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="self.close();">
<div class="thesaurus_confirm_bottom_block">
<input class="cancel_btn" type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="closeModal();">
<input class="validate_btn" type="button" id="ok_button" value="{{ 'boutton::valider' | trans }}" onclick="ok();">
</div>
</form>
</center>
{% else %}
@@ -171,13 +168,9 @@
<br/>
{% trans with {'%cfield%' : cfield} %}thesaurus:: A cet emplacement du thesaurus , un candidat du champ %cfield% ne peut etre accepte{% endtrans %}
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<input style="position:relative; z-index:2; width:100px" type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="self.close();">
<div class="thesaurus_confirm_bottom_block">
<input class="cancel_btn" type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="closeModal();">
</div>
{% endif %}
{% endif %}
{% endif %}

View File

@@ -13,12 +13,13 @@
switch(button)
{
case "submit":
document.forms[0].action = "export_" + format + ".php";
document.forms[0].submit();
$('.export-form').attr('action',"export_" + format + ".php");
$('.export-form').submit();
$('.close-dialog').trigger('click');
break;
case "cancel":
self.returnValue = null;
self.close();
$('.close-dialog').trigger('click');
break;
}
}
@@ -49,19 +50,22 @@
{
var i, f;
url = "./export_"+format+".php?bid={{ bid }}&piv={{ piv }}&id={{ id }}&typ={{ typ }}&dlg=0&smp=1";
url += "&osl=" + (document.forms[0].osl[0].checked ? "1" : "0");
url += "&iln=" + (document.forms[0].iln.checked ? "1" : "0");
url += "&hit=" + (document.forms[0].hit.checked ? "1" : "0");
url += "&ilg=" + (document.forms[0].ilg.checked ? "1" : "0");
url += "&osl=" + ($('.osl_1')[0].checked ? "1" : $('.osl_0')[0].checked ? "0" : "0");
url += "&iln=" + ($('.iln')[0].checked ? "1" : "0");
url += "&hit=" + ($('.hit')[0].checked ? "1" : "0");
url += "&ilg=" + ($('.ilg')[0].checked ? "1" : "0");
document.getElementById("ifrsample").src = url;
}
$( document ).ready(function() {
loaded();
});
</script>
</head>
<body onload="loaded();" class="dialog">
<body class="dialog">
<center>
<br/>
<form onsubmit="clkBut('submit');return(false);" action="export_topics.php" target="EXPORT2">
<input type="hidden" name="bid" value="{{ bid }}" >
<form class="export-form" action="export_topics.php" target="EXPORT2">
<input type="hidden" name="bid" value="{{ bid }}">
<input type="hidden" name="piv" value="{{ piv }}" >
<input type="hidden" name="id" value="{{ id }}" >
<input type="hidden" name="typ" value="{{ typ }}" >
@@ -82,23 +86,23 @@
</div>
<div id='subform_text' style="margin-left:10px;">
<div style="white-space:nowrap">
<input type='radio' name='osl' checked value='1' onclick="chgFormat();">
<input type='radio' name='osl' class="osl_1" checked value='1' onclick="chgFormat();">
{{ 'thesaurus:: exporter avec les synonymes sur la meme ligne' | trans }}
</div>
<div style="white-space:nowrap">
<input type='radio' name='osl' value='0' onclick="chgFormat();">
<input type='radio' name='osl' class="osl_0" value='0' onclick="chgFormat();">
{{ 'thesaurus:: exporter avec une ligne par synonyme' | trans }}
</div>
<div style="white-space:nowrap">
<input type='checkbox' name='iln' value='1' onclick="chgFormat();">
<input type='checkbox' name='iln' class="iln" value='1' onclick="chgFormat();">
{{ 'thesaurus:: export : numeroter les lignes' | trans }}
</div>
<div style="white-space:nowrap">
<input type='checkbox' name='ilg' value='1' onclick="chgFormat();">
<input type='checkbox' name='ilg' class="ilg" value='1' onclick="chgFormat();">
{{ 'thesaurus:: export : inclure la langue' | trans }}
</div>
<div style="white-space:nowrap">
<input type='checkbox' name='hit' value='1' onclick="chgFormat();">
<input type='checkbox' name='hit' class="hit" value='1' onclick="chgFormat();">
{{ 'thesaurus:: export : inclure les hits' | trans }}
</div>
</div>
@@ -110,10 +114,11 @@
</tbody>
</table>
<br/>
<br/>
<input type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');" style="width:100px;">
&nbsp;&nbsp;&nbsp;
<input type="button" id="submit_button" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');" style="width:100px;">
<div class="thesaurus_confirm_bottom_block">
<input type="button" id="cancel_button" class="cancel_btn" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');">
<input type="button" id="submit_button" class="validate_btn" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');">
</div>
</form>
</center>
</body>

View File

@@ -11,12 +11,12 @@
switch(button)
{
case "submit":
document.forms[0].target = (format == 'tofiles' ? "_self" : "EXPORT2");
document.forms[0].submit();
$('.export-topics-form').submit();
$('.close-dialog').trigger('click');
break;
case "cancel":
self.returnValue = null;
self.close();
$('.close-dialog').trigger('click');
break;
}
}
@@ -45,21 +45,25 @@
}
function chgFormat()
{
var i, f;
for(i=0; i<document.forms[0].ofm.length; i++)
var i;
for(i=0; i<$('.ofm').length; i++)
{
f = document.forms[0].ofm[i].value;
if(document.forms[0].ofm[i].checked)
{
format = f;
if($(".ofm_1")[0].checked) {
format = $(".ofm_1").val();
}
if($(".ofm_2")[0].checked) {
format = $(".ofm_2").val();
}
}
}
$( document ).ready(function() {
loaded();
});
</script>
</head>
<body onload="loaded();" class="dialog">
<body class="dialog">
<center>
<form onsubmit="clkBut('submit');return(false);" action="export_topics.php">
<form action="export_topics.php" class="export-topics-form" target="EXPORT2">
<input type="hidden" name="bid" value="{{ bid }}" >
<input type="hidden" name="piv" value="{{ piv }}" >
<input type="hidden" name="id" value="{{ id }}" >
@@ -67,15 +71,15 @@
<input type="hidden" name="dlg" value="{{ dlg }}" >
<input type="hidden" name="obr" value="{{ obr }}" >
<div style="padding:10px;">
<div style="padding: 5px 15px">
<div class="x3Dbox">
<span class="title">{{ 'thesaurus:: exporter' | trans }}</span>
<div style="white-space:nowrap">
<input type='radio' name='ofm' checked value='tofiles' onclick="chgFormat();">
<input type='radio' name='ofm' class="ofm ofm_1" checked value='tofiles' onclick="chgFormat();">
{{ 'thesaurus:: exporter vers topics pour toutes les langues' | trans }}
</div>
<div style="white-space:nowrap">
<input type='radio' name='ofm' value='toscreen' onclick="chgFormat();">
<input type='radio' name='ofm' value='toscreen' class="ofm ofm_2" onclick="chgFormat();">
{% trans with {'%piv%' : piv} %}thesaurus:: exporter a l'ecran pour la langue %piv%{% endtrans %}
</div>
</div>
@@ -85,7 +89,7 @@
<div class="x3Dbox">
<span class="title">{{ 'phraseanet:: tri' | trans }}</span>
<div style="white-space:nowrap">
<input type='checkbox' name='srt' checked onclick="chgFormat();">
<input type='checkbox' name='srt' checked >
{{ 'phraseanet:: tri par date' | trans }}
</div>
</div>
@@ -95,15 +99,15 @@
<div class="x3Dbox">
<span class="title">{{ 'thesaurus:: recherche' | trans }}</span>
<div style="white-space:nowrap">
<input type='radio' name='sth' value="1" checked onclick="chgFormat();">
<input type='radio' name='sth' value="1" checked >
{{ 'thesaurus:: recherche thesaurus *:"query"' | trans }}
</div>
<div style="white-space:nowrap">
<input type='radio' name='sth' value="0" onclick="chgFormat();">
<input type='radio' name='sth' value="0" >
{{ 'thesaurus:: recherche fulltext' | trans }}
</div>
<div style="white-space:nowrap">
<input type='checkbox' name='sand' onclick="chgFormat();">
<input type='checkbox' name='sand' >
{{ 'thesaurus:: question complete (avec operateurs)' | trans }}
</div>
</div>
@@ -113,30 +117,31 @@
<div class="x3Dbox">
<span class="title">{{ 'thesaurus:: presentation' | trans }}</span>
<div style="white-space:nowrap">
<input type='radio' name='obrf' value="from_itf_closable" checked onclick="chgFormat();">
<input type='radio' name='obrf' value="from_itf_closable" checked >
{{ 'thesaurus:: presentation : branches refermables' | trans }}
</div>
<div style="white-space:nowrap">
<input type='radio' name='obrf' value="from_itf_static" onclick="chgFormat();">
<input type='radio' name='obrf' value="from_itf_static" >
{{ 'thesaurus:: presentation : branche ouvertes' | trans }}
</div>
<div style="white-space:nowrap">
<input type='radio' name='obrf' value="all_opened_closable" onclick="chgFormat();">
<input type='radio' name='obrf' value="all_opened_closable" >
{{ 'thesaurus:: tout deployer - refermable' | trans }}
</div>
<div style="white-space:nowrap">
<input type='radio' name='obrf' value="all_opened_static" onclick="chgFormat();">
<input type='radio' name='obrf' value="all_opened_static" >
{{ 'thesaurus:: tout deployer - statique' | trans }}
</div>
<div style="white-space:nowrap">
<input type='radio' name='obrf' value="all_closed" onclick="chgFormat();">
<input type='radio' name='obrf' value="all_closed" >
{{ 'thesaurus:: tout fermer' | trans }}
</div>
</div>
</div>
<input type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');" style="width:100px;">
&nbsp;&nbsp;&nbsp;
<input type="button" id="submit_button" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');" style="width:100px;">
<div class="thesaurus_confirm_bottom_block">
<input type="button" id="cancel_button" class="cancel_btn" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');">
<input type="button" id="submit_button" class="validate_btn" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');">
</div>
</form>
</center>
</body>

View File

@@ -32,12 +32,9 @@
{% endfor %}
{% if ofm == 'tofiles' %}
<center>
<br/>
<br/>
<br/>
<input type="button" value="{{ 'boutton::fermer' | trans }}" onclick="self.close();" style="width:100px;">
</center>
<div class="thesaurus_confirm_bottom_block">
<input type="button" class="cancel_btn" value="{{ 'boutton::fermer' | trans }}" onclick=" self.close();" style="width:120px;">
</div>
{% endif %}
</div>
</body>

View File

@@ -15,13 +15,12 @@
{
switch(button)
{
case "submit":
document.forms[0].target='IFRIM';
document.forms[0].submit();
case "submit":=
$('.import-form').submit();
break;
case "cancel":
self.returnValue = null;
self.close();
$('.close-dialog').trigger('click');
break;
}
}
@@ -32,8 +31,7 @@
{
if(!err)
{
{{ opener }}.reload();
self.close();
$('.close-dialog').trigger('click');
}
else
{
@@ -44,7 +42,7 @@
</head>
<body onload="loaded();" class="dialog">
<br/>
<form onsubmit="clkBut('submit');return(false);" action="import.php" enctype="multipart/form-data" method="post">
<form onsubmit="clkBut('submit');return(false);" action="import.php" enctype="multipart/form-data" method="post" class="import-form" target="IFRIM">
<input type="hidden" name="bid" value="{{ bid }}" >
<input type="hidden" name="piv" value="{{ piv }}" >
<input type="hidden" name="id" value="{{ id }}" >
@@ -57,7 +55,7 @@
<input type="file" name="fil" /> (max 16Mo)
<br/>
<div style="text-align:center">
<div class="text-center">
<table>
<tr>
<td style="text-align:left"><input type="checkbox" disabled="disabled" name="dlk" checked="checked">{{ 'thesaurus:: supprimer les liens des champs tbranch' | trans }}</td>
@@ -67,10 +65,11 @@
</tr>
</table>
<br/>
<input type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');" style="width:100px;">
&nbsp;&nbsp;&nbsp;
<input type="button" id="submit_button" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');" style="width:100px;">
</div>
<div class="thesaurus_confirm_bottom_block">
<input type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');" >
<input type="button" id="submit_button" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');">
</div>
</divclass>
</form>
<iframe style="display:block; height:50px;" name="IFRIM"></iframe>
</body>

View File

@@ -25,41 +25,55 @@
switch(button)
{
case "submit":
// document.forms[0].target="LINKFIELD";
document.forms[0].submit();
$('.link-field-1').submit(
$.ajax({
url : 'linkfield2.php',
type : 'POST',
data : $('.link-field-1').serialize(),
success : function( data ) {
$("#DLG_LINK_FIELD_1").html('');
$("#DLG_LINK_FIELD_1").append(data);
},
error : function( xhr, err ) {
alert('Error');
}
})
);
break;
case "cancel":
self.close();
$('.close-dialog').trigger('click');
break;
}
}
function loaded()
{
window.name="LINKFIELD";
ckField();
}
$( document ).ready(function() {
loaded();
});
</script>
</head>
<body onload="loaded();" class="dialog">
<center>
<form action="linkfield2.php" method="post" target="LINKFIELD">
<input type="hidden" name="piv" value="{{ piv }}">
<input type="hidden" name="bid" value="{{ bid }}">
<input type="hidden" name="tid" value="{{ tid }}">
<body class="dialog">
{% set branch = "<br/><b>" ~ fullBranch ~ "</b><br/>" %}
{% trans with {'%branch%' : branch} %}thesaurus:: Lier la branche de thesaurus au champ %branch%{% endtrans %}
<form class="link-field-1" action="linkfield2.php" method="post" target="LINKFIELD" style="padding: 5px 15px">
<input type="hidden" name="piv" value="{{ piv }}">
<input type="hidden" name="bid" value="{{ bid }}">
<input type="hidden" name="tid" value="{{ tid }}">
<div style="width:70%; height:200px; overflow:scroll;" class="x3Dbox">
{% for fieldname, checked in fieldnames %}
<input type="checkbox" name="field[]" value="{{ fieldname }}" {% if checked %}checked{% endif %} ck0="{% if checked %}1{% else %}0{% endif %}" onclick="return(ckField());">{{ fieldname }}<br/>
{% endfor %}
{% set branch = "<br/><b>" ~ fullBranch ~ "</b><br/>" %}
{% trans with {'%branch%' : branch} %}thesaurus:: Lier la branche de thesaurus au champ %branch%{% endtrans %}
<div style="width:100%; height:270px; overflow:scroll;" class="x3Dbox">
{% for fieldname, checked in fieldnames %}
<input type="checkbox" name="field[]" value="{{ fieldname }}" {% if checked %}checked{% endif %} ck0="{% if checked %}1{% else %}0{% endif %}" onclick="return(ckField());">{{ fieldname }}<br/>
{% endfor %}
</div>
<div class="thesaurus_confirm_bottom_block">
<input type="button" id="cancel_button" class="cancel_btn" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');">
<input type="button" id="submit_button" class="validate_btn" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');">
</div>
<br/>
<input type="button" id="submit_button" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');">
&nbsp;&nbsp;&nbsp;
<input type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');">
</form>
</center>
</body>
</html>

View File

@@ -12,10 +12,23 @@
switch(button)
{
case "submit":
document.forms[0].submit();
$('.link-field-2').submit(
$.ajax({
url : 'linkfield3.php',
type : 'POST',
data : $('.link-field-2').serialize(),
success : function( data ) {
$("#DLG_LINK_FIELD_1").html('');
$("#DLG_LINK_FIELD_1").append(data);
},
error : function( xhr, err ) {
alert('Error');
}
})
);
break;
case "cancel":
self.close();
$('.close-dialog').trigger('click');
break;
}
}
@@ -23,11 +36,15 @@
{
window.name="LINKFIELD";
}
$( document ).ready(function() {
loaded();
});
</script>
</head>
<body onload="loaded();" class="dialog">
<body class="dialog">
<center>
<form action="linkfield3.php" method="post" target="LINKFIELD">
<form class="link-field-2" action="linkfield3.php" method="post" target="LINKFIELD">
<div class="text-center">
<input type="hidden" name="piv" value="{{ piv }}">
<input type="hidden" name="bid" value="{{ bid }}">
<input type="hidden" name="tid" value="{{ tid }}">
@@ -68,10 +85,11 @@
{% else %}
<div style='position:absolute; top:5px; left:0px; width:100%; text-align:center; color:green'>{{ 'thesaurus:: pas de reindexation' | trans }}</div>
{% endif %}
<br/>
<input type="button" id="submit_button" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');">
&nbsp;&nbsp;&nbsp;
<input type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');">
</div>
<div class="thesaurus_confirm_bottom_block">
<input type="button" id="cancel_button" class="cancel_btn" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');">
<input type="button" id="submit_button" class="validate_btn" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');">
</div>
</form>
</center>
</body>

View File

@@ -41,8 +41,9 @@
<br/>
{% endif %}
</div>
<br/>
<input type="button" value="{{ 'boutton::fermer' | trans }}" onclick="self.close();">
<div class="thesaurus_confirm_bottom_block">
<input type="button" value="{{ 'boutton::fermer' | trans }}" class="cancel_btn" onclick="$('.close-dialog').trigger('click');">
</div>
</form>
</center>
</body>

View File

@@ -8,6 +8,7 @@
{#<script type="text/javascript" src="{{ path('minifier', { 'f' : 'skins/thesaurus/xmlhttp.js' }) }}"></script>#}
</head>
<body onload="loaded();" class="dialog" style="text-align:center">
<div class="text-center">
{% if dlg is not none %}
{% set opener = 'window.dialogArguments.win' %}
{% else %}
@@ -37,8 +38,9 @@
<br/>
{{ prop_label }}
<br/>
<center>
<form onsubmit="return(false);">
</div>
<div class="text-center">
<form onsubmit="return(false);" class="thesaurus_confirm_bottom_block">
<input type="hidden" name="bid" value="{{ bid }} ">
<input type="hidden" name="pid" value="{{ pid }}">
<div class='x3Dbox' style='margin:15px; height:100px; overflow:auto;'>
@@ -50,34 +52,27 @@
{{ 'thesaurus:: selectionner la provenance a accepter' | trans }}
{% endif %}
<br/>
<input type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');" style="width:100px;">
<input type="button" id="cancel_button" class="cancel_btn" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');" >
&nbsp;&nbsp;&nbsp;
<input type="button" id="submit_button" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');" style="width:100px;">
<input type="button" id="submit_button" class="validate_btn" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');">
</form>
</center>
</div>
{% else %}
{% if nb_candidates_bad > 0 %}
{% set prop_label = 'thesaurus:: est candidat en provenance des champs mais ne peut etre accepte a cet emplacement du thesaurus' | trans %}
{% else %}
{% set prop_label = 'thesaurus:: n\'est pas present dans les candidats' | trans %}
{% endif %}
<br/>
<br/>
<br/>
<br/>
<br/>
{{ zterm }}
<br/>
<br/>
{{ prop_label }}
<br/>
<br/>
<br/>
<br/>
<form>
<input type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');" style="width:100px;">
<form class="thesaurus_confirm_bottom_block">
<input type="button" id="cancel_button" class="cancel_btn" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');" >
&nbsp;&nbsp;&nbsp;
<input type="button" id="submit_button" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');" style="width:100px;">
<input type="button" id="submit_button" class="validate_btn" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');" >
</form>
{% endif %}
</body>
@@ -119,17 +114,19 @@
switch(refresh.item(i).getAttribute("type"))
{
case "CT":
{{ opener }}.reloadCtermsBranch(refresh.item(i).getAttribute("id"));
reloadCtermsBranch(refresh.item(i).getAttribute("id"));
break;
case "TH":
{{ opener }}.reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
break;
}
}
self.close();
$("#NEWSY_DLG_CONFIRM").dialog('close');
$("#NEWSY_DLG_CONFIRM").html('');
break;
case "cancel":
self.close();
$("#NEWSY_DLG_CONFIRM").dialog('close');
$("#NEWSY_DLG_CONFIRM").html('');
break;
}
}
@@ -141,18 +138,18 @@
switch(button)
{
case "submit":
{% if typ == "TS" %}
url = "xmlhttp/newts.x.php";
{% else %}
url = "xmlhttp/newsy.x.php";
{% endif %}
{% if typ == "TS" %}
url = "xmlhttp/newts.x.php";
{% else %}
url = "xmlhttp/newsy.x.php";
{% endif %}
parms = "bid={{ bid }}";
parms += "&piv={{ piv }}";
parms += "&pid={{ pid }}";
parms += "&t={{ term | url_encode }}";
{% if context is not none %}
parms += "&k={{ context | url_encode }}";
{% endif %}
{% if context is not none %}
parms += "&k={{ context | url_encode }}";
{% endif %}
parms += "&sylng={{ sylng }}";
parms += "&reindex=0";
@@ -163,17 +160,20 @@
switch(refresh.item(i).getAttribute("type"))
{
case "CT":
{{ opener }}.reloadCtermsBranch(refresh.item(i).getAttribute("id"));
$("#NEWSY_DLG_CONFIRM").dialog('close');
reloadCtermsBranch(refresh.item(i).getAttribute("id"));
break;
case "TH":
{{ opener }}.reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
$("#NEWSY_DLG_CONFIRM").dialog('close');
reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
break;
}
}
self.close();
break;
case "cancel":
self.close();
$("#NEWSY_DLG_CONFIRM").dialog('close');
$("#NEWSY_DLG_CONFIRM").html('');
break;
}
}

View File

@@ -3,11 +3,6 @@
{% else %}
{% set opener = "opener" %}
{% endif %}
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="{{ app['locale'] }}">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{ 'thesaurus:: Proprietes' | trans }}</title>
<link type="text/css" rel="stylesheet" href="/assets/thesaurus/css/thesaurus{% if not app.debug %}.min{% endif %}.css" />
<style type="text/css">
a
@@ -24,38 +19,31 @@
font-weight:900;
}
</style>
<script type="text/javascript" src="/assets/vendors/jquery/jquery{% if not app.debug %}.min{% endif %}.js"></script>
<script type="text/javascript" src="/assets/thesaurus/js/thesaurus{% if not app.debug %}.min{% endif %}.js"></script>
<script type="text/javascript">
function loaded()
{
window.name="PROPERTIES";
self.focus();
}
$( document ).ready(function() {
loaded();
});
</script>
</head>
<body id="desktop" onload="loaded();" class="dialog">
<div class="menu" id="flagsMenu" style="z-index:50">
{% for code, language in languages %}
<a id='flagMenu_{{ code }}' href='javascript:void(0)' class=''>
<img src='/assets/common/images/lng/{{ code }}_flag_18.gif' />{{ language }}</a>
{% endfor %}
</div>
<div class="menu" id="syMenu" style="z-index:50">
<a href="javascript:void(0)" id="delete_sy">{{ 'thesaurus::menu: supprimer' | trans }}</a>
</div>
<div class="menu" id="syMenu" style="z-index:999">
<a href="javascript:void(0)" id="delete_sy">{{ 'thesaurus:properties:: Mettre dans le stock' | trans }}</a>
</div>
<div id="desktop" class="dialog">
<div style='text-align:right'>
<H4>{{ fullpath | raw }}</H4>
<div style='float:right'>
<b>id:</b>&nbsp;{{ id }}
</div>
<H4>{{ fullpath | raw }}</H4><br/>
{% if typ == "CT" %}
<br/>
{% elseif typ == "TH" %}
{#{% elseif typ == "TH" %}
{% trans with {'%hits%' : hits} %}thesaurus:: %hits% reponses retournees{% endtrans %}
<br/>
<br/>
<br/>#}
{% endif %}
<div id="TSY" class="tableContainer" style="margin:10px; position:relative; top:0px; left:0px">
<div>
@@ -71,9 +59,7 @@
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>{{ 'thesaurus:: synonymes' | trans }}</th>
<th>{{ 'thesaurus:: hits' | trans }}</th>
<th>{{ 'thesaurus:: ids' | trans }}</th>
<th></th>
<th colspan="3">{{ 'thesaurus:termePorperties:termeId' | trans }}</th>
</tr>
</thead>
</table>
@@ -99,13 +85,13 @@
{% endif %}
</td>
{% if data['lng'] %}
<td id='FLG_{{ data['id'] }}'><img src='/assets/common/images/lng/{{ data['lng'] }}_flag_18.gif' /></td>
<td id='FLG_{{ data['id'] }}'>{{ data['lng'] }}</td>
{% else %}
<td id='FLG_{{ data['id'] }}'><img src='/assets/thesaurus/images/noflag.gif' /></td>
<td id='FLG_{{ data['id'] }}'></td>
{% endif %}
<td>{{ data['t'] }}</td>
<td>{{ data['hits'] }}</td>
<td>{{ data['id'] }}</td>
<td><span class="delete_term"></span></td>
</tr>
{% endfor %}
</tbody>
@@ -115,13 +101,17 @@
</div>
<center>
<form onsubmit="return(false);">
<input style="position:relative; z-index:2" type="button" id="close_button" value="{{ 'boutton::fermer' | trans }}" onclick="self.close();">
<div class="thesaurus_confirm_bottom_block">
<input type="button" id="close_button" class="close_button cancel_btn" value="{{ 'boutton::fermer' | trans }}" >
</div>
</form>
</center>
<script type="text/javascript">
$(".close_button").on("click", function(){
$('.close-dialog').trigger('click');
});
// gui callback du menu des drapeaux
var nsy = {{ synonyms | length }};
function cbME_flags(action, cbParm, menuelem_id)
{
if(action != "SELECT" || !menuelem_id) {
@@ -148,17 +138,23 @@
switch(refresh.item(i).getAttribute("type"))
{
case "CT":
{{ opener }}.reloadCtermsBranch(refresh.item(i).getAttribute("id"));
{{ opener }}.myGUI.select({{ opener }}.document.getElementById("THE_{{ id }}"));
reloadCtermsBranch(refresh.item(i).getAttribute("id"));
myGUI.select(document.getElementById("THE_{{ id }}"));
break;
case "TH":
{{ opener }}.reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
{{ opener }}.myGUI.select({{ opener }}.document.getElementById("THE_{{ id }}"));
reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
myGUI.select(document.getElementById("THE_{{ id }}"));
break;
}
}
}
$('.delete_term').click(function (e) {
e.preventDefault();
$('#delete_sy').trigger('click');
});
// gui callback du menu des synonymes
function cbME_synonym(action, cbParm, menuelem_id)
{
@@ -169,6 +165,7 @@
// pas d'action possible s'il ne reste qu'un seul synonyme
// alert(nsy);
document.getElementById("delete_sy").className = "disabled";
$('.delete_term').addClass('disabled');
// document.getElementById("reject_sy").className = "disabled";
}
else
@@ -178,12 +175,14 @@
// y'a des hits, on peut pas supprimer
// document.getElementById("reject_sy").className = "";
document.getElementById("delete_sy").className = "";
$('.delete_term').removeClass('disabled');
}
else
{
// pas de hits : on peut supprimer
// document.getElementById("reject_sy").className = "";
document.getElementById("delete_sy").className = "";
$('.delete_term').removeClass('disabled');
}
}
return;
@@ -216,7 +215,7 @@
ret = loadXMLDoc(url, parms, true);
sy_list = ret.getElementsByTagName("sy_list").item(0);
refresh_sy(sy_list);
//refresh_sy(sy_list);
refresh = ret.getElementsByTagName("refresh");
for(i=0; i<refresh.length; i++)
@@ -224,15 +223,24 @@
switch(refresh.item(i).getAttribute("type"))
{
case "CT":
{{ opener }}.reloadCtermsBranch(refresh.item(i).getAttribute("id"));
{{ opener }}.myGUI.select({{ opener }}.document.getElementById("THE_{{ id }}"));
reloadCtermsBranch(refresh.item(i).getAttribute("id"));
myGUI.select(document.getElementById("THE_{{ id }}"));
break;
case "TH":
{{ opener }}.reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
{{ opener }}.myGUI.select({{ opener }}.document.getElementById("THE_{{ id }}"));
reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
myGUI.select(document.getElementById("THE_{{ id }}"));
break;
}
}
var new_url = $('#url_properties').val();
$.ajax({
type: "GET",
url: `${new_url}`,
success: function(data){
$('#DLG_PROPERTIES').html('');
$('#DLG_PROPERTIES').append(data);
}
});
}
break;
}
@@ -273,16 +281,16 @@
td = tr.appendChild(document.createElement("td"));
td.id = "FLG_"+(nsy+1);
// td.innerText = n.getAttribute("lng");
img = td.appendChild(document.createElement("img"));
img.setAttribute("src", "/assets/common/images/lng/"+n.getAttribute("lng")+"_flag_18.gif");
span = td.appendChild(document.createElement("span"));
span.innerHTML = n.getAttribute("lng");
td = tr.appendChild(document.createElement("td"));
// td.colSpan = "2";
// td.setAttribute("colSpan", "3"); // attention au 'S' majuscule !!!
td.innerHTML = n.getAttribute("t");
td = tr.appendChild(document.createElement("td"));
td.innerHTML = n.getAttribute("hits");
/*td = tr.appendChild(document.createElement("td"));
td.innerHTML = n.getAttribute("hits");*/
td = tr.appendChild(document.createElement("td"));
td.innerHTML = n.getAttribute("id");
@@ -308,15 +316,6 @@
;
if(tr)
myGUI.select(tr);
switch(o.id.substr(0, 4))
{
case "FLG_": // le drapeau
document.getElementById("flagsMenu").runAsMenu( evt, tr );
break;
case "SYN_": // le synonyme
document.getElementById("syMenu").runAsMenu( evt, tr );
break;
}
}
break;
case "MOUSEDOWN":
@@ -335,6 +334,21 @@
syChgPos(-1);
break;
}
switch(o.id.substr(0, 4))
{
case "FLG_": // le drapeau
document.getElementById("flagsMenu").runAsMenu( evt, tr );
break;
case "SYN_": // le synonyme
if (stock == false)
{
document.getElementById("syMenu").runAsMenu(evt, tr);
$('.delete_term').html('');
$('.delete_term', tr).append($('#syMenu').html());
}
break;
}
}
break;
case "DBLCLICK":
@@ -368,12 +382,12 @@
switch(refresh.item(i).getAttribute("type"))
{
case "CT":
{{ opener }}.reloadCtermsBranch(refresh.item(i).getAttribute("id"));
{{ opener }}.myGUI.select({{ opener }}.document.getElementById("THE_{{ id }}"));
reloadCtermsBranch(refresh.item(i).getAttribute("id"));
myGUI.select(document.getElementById("THE_{{ id }}"));
break;
case "TH":
{{ opener }}.reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
{{ opener }}.myGUI.select({{ opener }}.document.getElementById("THE_{{ id }}"));
reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
myGUI.select(document.getElementById("THE_{{ id }}"));
break;
}
}
@@ -383,7 +397,5 @@
myGUI.setClickable("TSY", cbDD_TSY);
myGUI.setAsMenu("flagsMenu", cbME_flags);
myGUI.setAsMenu("syMenu", cbME_synonym);
</script>
</body>
</html>
</div>

Some files were not shown because too many files have changed in this diff Show More