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 # Registry from where you pull Docker images
PHRASEANET_DOCKER_REGISTRY=local PHRASEANET_DOCKER_REGISTRY=local
# Tag of the Docker images # Tag of the Docker images
PHRASEANET_DOCKER_TAG=latest PHRASEANET_DOCKER_TAG=4.1.1
# APPLICATION PORT # APPLICATION PORT
PHRASEANET_APP_PORT=8082 PHRASEANET_APP_PORT=8082
# RabbitMQ configuration # RabbitMQ configuration
@@ -13,6 +13,8 @@ RABBITMQ_MANAGEMENT_PORT=10811
MYSQL_ROOT_PASSWORD=root MYSQL_ROOT_PASSWORD=root
SERVER_NAME=phraseanet-docker SERVER_NAME=phraseanet-docker
# --------------- GATEWAY TIMEOUT -----------------------
GATEWAY_SEND_TIMEOUT=120
# --------------- PHP CONFIGURATION -------------------- # --------------- PHP CONFIGURATION --------------------
@@ -21,6 +23,9 @@ MAX_BODY_SIZE=2G
# Max input var # Max input var
MAX_INPUT_VARS=12000 MAX_INPUT_VARS=12000
MAX_EXECUTION_TIME=120
MAX_INPUT_TIME=60
# Enable opcache ? (0/1) # Enable opcache ? (0/1)
OPCACHE_ENABLED=1 OPCACHE_ENABLED=1
# session cache limiter (off/on) # session cache limiter (off/on)
@@ -34,6 +39,8 @@ PHP_LOG_LEVEL=warning
# These variables are used in the configuration.yml . # These variables are used in the configuration.yml .
# set here the first user / email couple # 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_EMAIL=admin@alchemy.fr
PHRASEANET_ADMIN_ACCOUNT_PASSWORD=iJRqXU0MwbyJewQLBbra6IWHsWly PHRASEANET_ADMIN_ACCOUNT_PASSWORD=iJRqXU0MwbyJewQLBbra6IWHsWly
# Database parameters # Database parameters
@@ -64,6 +71,16 @@ PHRASEANET_SMTP_SECURE_MODE=tls
PHRASEANET_SMTP_USER= PHRASEANET_SMTP_USER=
PHRASEANET_SMTP_PASSWORD= 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 --- # --- DEV purpose ---
@@ -86,6 +103,7 @@ PHRASEANET_DB_DIR=./volumes/db
PHRASEANET_ELASTICSEARCH_DIR=./volumes/elasticsearch PHRASEANET_ELASTICSEARCH_DIR=./volumes/elasticsearch
PHRASEANET_THUMBNAILS_DIR=./www/thumbnails PHRASEANET_THUMBNAILS_DIR=./www/thumbnails
PHRASEANET_CUSTOM_DIR=./www/custom PHRASEANET_CUSTOM_DIR=./www/custom
PHRASEANET_PLUGINS_DIR=./www/plugins
PHRASEANET_TMP_DIR=./tmp PHRASEANET_TMP_DIR=./tmp
PHRASEANET_CACHE_DIR=./cache PHRASEANET_CACHE_DIR=./cache
PHRASEANET_DOWNLOAD_DIR=./datas/download PHRASEANET_DOWNLOAD_DIR=./datas/download

View File

@@ -1,5 +1,155 @@
# CHANGELOG # 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 ## 4.0.9
### Adds ### Adds
@@ -13,11 +163,11 @@
### Fixes ### Fixes
* PHRAS-2491 - Front - Click on facets title (expand/collapse) launched a bad query, due to jquery error. - 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-2510 - Front - Facets values appear Truncated after 15th character.
* PHRAS-2153 - Front - No user search possible with the field "Company" and field "Country". - 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-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-2538 - Back - Some MP4 files were not correctly detected by Phraseanet.
## 4.0.8 ## 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 \ swftools \
unoconv \ unoconv \
unzip \ unzip \
xpdf \ poppler-utils \
libreoffice-base-core \ libreoffice-base-core \
libreoffice-impress \ libreoffice-impress \
libreoffice-calc \ libreoffice-calc \
@@ -67,6 +67,7 @@ RUN echo "deb http://deb.debian.org/debian stretch main non-free" > /etc/apt/sou
libgsm1-dev \ libgsm1-dev \
libfreetype6-dev \ libfreetype6-dev \
# End FFmpeg # End FFmpeg
nano \
&& update-locale "LANG=fr_FR.UTF-8 UTF-8" \ && update-locale "LANG=fr_FR.UTF-8 UTF-8" \
&& dpkg-reconfigure --frontend noninteractive locales \ && dpkg-reconfigure --frontend noninteractive locales \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && 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 ## Prerequisites
- docker-compose - docker-compose >=v1.25.4
- docker >=v18.01-ce - 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 ## Get started
You should review the default env variables defined in `.env` file. 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: 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` 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/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 To use them and not build the images locally, we advise to override the properties in file: env.local
```bash ```bash
@@ -108,6 +119,21 @@ PHRASEANET_DOCKER_REGISTRY=alchemyfr
# Tag of the Docker images # Tag of the Docker images
PHRASEANET_DOCKER_TAG= 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 ## 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: Plugins can be installed during build if you set the `PHRASEANET_PLUGINS` env var as follows:
```bash ```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 # You can optionally precise the branch to install
# If not precised, the main branch will be pulled # If not precised, the main branch will be pulled
PHRASEANET_PLUGINS="git@github.com:alchemy-fr/Phraseanet-plugin-webgallery.git(custom-branch)" PHRASEANET_PLUGINS="git@github.com:alchemy-fr/Phraseanet-plugin-webgallery.git(custom-branch)"
# Plugins are separated by spaces # Plugins are separated by semicolons
PHRASEANET_PLUGINS="git@github.com:foo/bar.git(branch-1) git@github.com:baz/42.git" 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: 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 ```bash
export PHRASEANET_SSH_PRIVATE_KEY=$(cat ~/.ssh/id_rsa) export PHRASEANET_SSH_PRIVATE_KEY=$(cat ~/.ssh/id_rsa)
# or if your private key is protected by a passphrase: # or if your private key is protected by a passphrase:

View File

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

View File

@@ -13,10 +13,6 @@ services:
- ../:/var/alchemy - ../:/var/alchemy
- .:/var/alchemy/Phraseanet - .:/var/alchemy/Phraseanet
- ./docker/nginx/root/entrypoint.sh:/entrypoint.sh - ./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: builder:
build: build:
@@ -50,25 +46,12 @@ services:
volumes: volumes:
- ../:/var/alchemy - ../:/var/alchemy
- .:/var/alchemy/Phraseanet - .:/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: worker:
volumes: volumes:
- ../:/var/alchemy - ../:/var/alchemy
- .:/var/alchemy/Phraseanet - .:/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: rabbitmq:
ports: ports:

View File

@@ -14,10 +14,12 @@ services:
- ${PHRASEANET_DATA_DIR}:/var/alchemy/Phraseanet/datas:rw - ${PHRASEANET_DATA_DIR}:/var/alchemy/Phraseanet/datas:rw
- ${PHRASEANET_THUMBNAILS_DIR}:/var/alchemy/Phraseanet/www/thumbnails:rw - ${PHRASEANET_THUMBNAILS_DIR}:/var/alchemy/Phraseanet/www/thumbnails:rw
- ${PHRASEANET_CUSTOM_DIR}:/var/alchemy/Phraseanet/www/custom:rw - ${PHRASEANET_CUSTOM_DIR}:/var/alchemy/Phraseanet/www/custom:rw
- ${PHRASEANET_PLUGINS_DIR}:/var/alchemy/Phraseanet/www/plugins:rw
depends_on: depends_on:
- phraseanet - phraseanet
environment: environment:
- MAX_BODY_SIZE - MAX_BODY_SIZE
- GATEWAY_SEND_TIMEOUT
ports: ports:
- ${PHRASEANET_APP_PORT}:80 - ${PHRASEANET_APP_PORT}:80
@@ -39,9 +41,12 @@ services:
- PHRASEANET_PROJECT_NAME - PHRASEANET_PROJECT_NAME
- MAX_BODY_SIZE - MAX_BODY_SIZE
- MAX_INPUT_VARS - MAX_INPUT_VARS
- MAX_EXECUTION_TIME
- MAX_INPUT_TIME
- OPCACHE_ENABLED - OPCACHE_ENABLED
- SESSION_CACHE_LIMITER - SESSION_CACHE_LIMITER
- PHP_LOG_LEVEL - PHP_LOG_LEVEL
- PHRASEANET_ADMIN_ACCOUNT_ID
- PHRASEANET_ADMIN_ACCOUNT_EMAIL - PHRASEANET_ADMIN_ACCOUNT_EMAIL
- PHRASEANET_ADMIN_ACCOUNT_PASSWORD - PHRASEANET_ADMIN_ACCOUNT_PASSWORD
- PHRASEANET_DB_HOST - PHRASEANET_DB_HOST
@@ -63,14 +68,28 @@ services:
- PHRASEANET_SMTP_SECURE_MODE - PHRASEANET_SMTP_SECURE_MODE
- PHRASEANET_SMTP_USER - PHRASEANET_SMTP_USER
- PHRASEANET_SMTP_PASSWORD - 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: volumes:
- config_vol:/var/alchemy/Phraseanet/config:rw - ${PHRASEANET_CONFIG_DIR}:/var/alchemy/Phraseanet/config:rw
- data_vol:/var/alchemy/Phraseanet/datas:rw - ${PHRASEANET_LOGS_DIR}:/var/alchemy/Phraseanet/logs:rw
- tmp_vol:/var/alchemy/Phraseanet/tmp:rw - ${PHRASEANET_DATA_DIR}:/var/alchemy/Phraseanet/datas:rw
- logs_vol:/var/alchemy/Phraseanet/logs:rw - ${PHRASEANET_THUMBNAILS_DIR}:/var/alchemy/Phraseanet/www/thumbnails:rw
- thumbnails_vol:/var/alchemy/Phraseanet/www/thumbnails:rw - ${PHRASEANET_CUSTOM_DIR}:/var/alchemy/Phraseanet/www/custom:rw
- custom_vol:/var/alchemy/Phraseanet/www/custom:rw - ${PHRASEANET_PLUGINS_DIR}:/var/alchemy/Phraseanet/www/plugins:rw
- cache_vol:/var/alchemy/Phraseanet/cache:rw - ${PHRASEANET_CACHE_DIR}:/var/alchemy/Phraseanet/cache:rw
- ${PHRASEANET_TMP_DIR}:/var/alchemy/Phraseanet/tmp:rw
worker: worker:
build: build:
@@ -93,14 +112,23 @@ services:
- OPCACHE_ENABLED - OPCACHE_ENABLED
- SESSION_CACHE_LIMITER - SESSION_CACHE_LIMITER
- PHP_LOG_LEVEL - 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: volumes:
- config_vol:/var/alchemy/Phraseanet/config:rw - ${PHRASEANET_CONFIG_DIR}:/var/alchemy/Phraseanet/config:rw
- data_vol:/var/alchemy/Phraseanet/datas:rw - ${PHRASEANET_LOGS_DIR}:/var/alchemy/Phraseanet/logs:rw
- tmp_vol:/var/alchemy/Phraseanet/tmp:rw - ${PHRASEANET_DATA_DIR}:/var/alchemy/Phraseanet/datas:rw
- logs_vol:/var/alchemy/Phraseanet/logs:rw - ${PHRASEANET_THUMBNAILS_DIR}:/var/alchemy/Phraseanet/www/thumbnails:rw
- thumbnails_vol:/var/alchemy/Phraseanet/www/thumbnails:rw - ${PHRASEANET_CUSTOM_DIR}:/var/alchemy/Phraseanet/www/custom:rw
- custom_vol:/var/alchemy/Phraseanet/www/custom:rw - ${PHRASEANET_CACHE_DIR}:/var/alchemy/Phraseanet/cache:rw
- cache_vol:/var/alchemy/Phraseanet/cache:rw - ${PHRASEANET_TMP_DIR}:/var/alchemy/Phraseanet/tmp:rw
db: db:
image: $PHRASEANET_DOCKER_REGISTRY/phraseanet-db:$PHRASEANET_DOCKER_TAG image: $PHRASEANET_DOCKER_REGISTRY/phraseanet-db:$PHRASEANET_DOCKER_TAG
@@ -151,10 +179,12 @@ volumes:
driver: local driver: local
custom_vol: custom_vol:
driver: local driver: local
plugins_dir:
driver: local
cache_vol: cache_vol:
driver: local driver: local
# to be replacer by stdout/stderr # to be replacer by stdout/stderr
logs_vol: logs_vol:
driver: local driver: local
dev_vol: dev_vol:
driver: local driver: local

View File

@@ -2,6 +2,6 @@
set -xe 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 "$@" exec "$@"

View File

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

View File

@@ -12,26 +12,33 @@ chown -R app:app \
datas \ datas \
tmp \ tmp \
logs \ logs \
www/thumbnails \ www
www/custom
FILE=config/configuration.yml FILE=config/configuration.yml
if [ -f "$FILE" ]; then if [ -f "$FILE" ]; then
echo "$FILE exists, skip setup." echo "$FILE exists, skip setup."
bin/setup system:config set registry.general.title $PHRASEANET_PROJECT_NAME if [[ $PHRASEANET_PROJECT_NAME ]]; then
if [[ $PHRASEANET_SMTP_ENABLED=true ]]; 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-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-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-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-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-port $PHRASEANET_SMTP_PORT
bin/setup system:config set registry.email.smtp-user $PHRASEANET_SMTP_USER 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.smtp-password $PHRASEANET_SMTP_PASSWORD
bin/setup system:config set registry.email.emitter-email $PHRASEANET_EMITTER_EMAIL bin/setup system:config set registry.email.emitter-email $PHRASEANET_EMITTER_EMAIL
bin/setup system:config set registry.email.prefix $PHRASEANET_MAIL_OBJECT_PREFIX 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 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 else
echo "$FILE doesn't exist, entering setup..." echo "$FILE doesn't exist, entering setup..."
runuser app -c docker/phraseanet/auto-install.sh runuser app -c docker/phraseanet/auto-install.sh
@@ -43,5 +50,18 @@ if [ ${XDEBUG_ENABLED} == "1" ]; then
fi fi
./docker/phraseanet/plugins/console init ./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 $@ bash -e docker-php-entrypoint $@

View File

@@ -380,7 +380,7 @@ expose_php = On
; Maximum execution time of each script, in seconds ; Maximum execution time of each script, in seconds
; http://php.net/max-execution-time ; http://php.net/max-execution-time
; Note: This directive is hardcoded to 0 for the CLI SAPI ; 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 ; 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 ; 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) ; Development Value: 60 (60 seconds)
; Production Value: 60 (60 seconds) ; Production Value: 60 (60 seconds)
; http://php.net/max-input-time ; http://php.net/max-input-time
max_input_time = 60 max_input_time = $MAX_INPUT_TIME
; Maximum input variable nesting level ; Maximum input variable nesting level
; http://php.net/max-input-nesting-level ; http://php.net/max-input-nesting-level

View File

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

View File

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

View File

@@ -2093,7 +2093,7 @@ class V1Controller extends Controller
try { try {
$collection = \collection::getByBaseId($this->app, $request->get('base_id')); $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(); return Result::create($request, ["record" => $this->listRecord($request, $record)])->createResponse();
} catch (\Exception $e) { } 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 <?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\Collection\Reference\CollectionReference;
use Alchemy\Phrasea\Controller\Api\Result;
use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Databox\DataboxGroupable; use Alchemy\Phrasea\Databox\DataboxGroupable;
use Alchemy\Phrasea\Fractal\CallbackTransformer; use Alchemy\Phrasea\Fractal\CallbackTransformer;
@@ -31,34 +34,19 @@ use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
use Alchemy\Phrasea\SearchEngine\SearchEngineLogger; use Alchemy\Phrasea\SearchEngine\SearchEngineLogger;
use Alchemy\Phrasea\SearchEngine\SearchEngineOptions; use Alchemy\Phrasea\SearchEngine\SearchEngineOptions;
use Alchemy\Phrasea\SearchEngine\SearchEngineResult; use Alchemy\Phrasea\SearchEngine\SearchEngineResult;
use caption_record;
use League\Fractal\Manager as FractalManager;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
use media_Permalink_Adapter;
use media_subdef;
use record_adapter;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class V3Controller extends Controller class V3SearchController extends Controller
{ {
/** use JsonBodyAware;
* Return detailed information about one story use DispatcherAware;
*
* @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'));
}
}
/** /**
* Search for results * Search for results
@@ -101,7 +89,7 @@ class V3Controller extends Controller
$includeResolver = new IncludeResolver($transformerResolver); $includeResolver = new IncludeResolver($transformerResolver);
$fractal = new \League\Fractal\Manager(); $fractal = new FractalManager();
$fractal->setSerializer(new TraceableArraySerializer($this->app['dispatcher'])); $fractal->setSerializer(new TraceableArraySerializer($this->app['dispatcher']));
$fractal->parseIncludes($this->resolveSearchIncludes($request)); $fractal->parseIncludes($this->resolveSearchIncludes($request));
@@ -125,314 +113,7 @@ class V3Controller extends Controller
return Result::create($request, $ret)->createResponse(); 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 * Returns requested includes
* *
* @param Request $request * @param Request $request
@@ -660,7 +341,7 @@ class V3Controller extends Controller
} }
/** /**
* @param RecordCollection|\record_adapter[] $references * @param RecordCollection|record_adapter[] $references
* @return RecordView[] * @return RecordView[]
*/ */
private function buildRecordViews($references) private function buildRecordViews($references)
@@ -693,7 +374,7 @@ class V3Controller extends Controller
foreach ($subdefGroups as $index => $subdefGroup) { foreach ($subdefGroups as $index => $subdefGroup) {
if (!isset($subdefGroup['thumbnail'])) { 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; $fakeSubdefs[spl_object_hash($fakeSubdef)] = $fakeSubdef;
$subdefGroups[$index]['thumbnail'] = $fakeSubdef; $subdefGroups[$index]['thumbnail'] = $fakeSubdef;
@@ -701,9 +382,9 @@ class V3Controller extends Controller
} }
$allSubdefs = $this->mergeGroupsIntoOneList($subdefGroups); $allSubdefs = $this->mergeGroupsIntoOneList($subdefGroups);
$allPermalinks = \media_Permalink_Adapter::getMany( $allPermalinks = media_Permalink_Adapter::getMany(
$this->app, $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)]); return !isset($fakeSubdefs[spl_object_hash($subdef)]);
}) })
); );
@@ -712,7 +393,7 @@ class V3Controller extends Controller
$subdefViews = []; $subdefViews = [];
/** @var \media_subdef $subdef */ /** @var media_subdef $subdef */
foreach ($allSubdefs as $index => $subdef) { foreach ($allSubdefs as $index => $subdef) {
$subdefView = new SubdefView($subdef); $subdefView = new SubdefView($subdef);
@@ -728,7 +409,7 @@ class V3Controller extends Controller
$reorderedGroups = []; $reorderedGroups = [];
/** @var \media_subdef[] $subdefGroup */ /** @var media_subdef[] $subdefGroup */
foreach ($subdefGroups as $index => $subdefGroup) { foreach ($subdefGroups as $index => $subdefGroup) {
$reordered = []; $reordered = [];
@@ -789,7 +470,7 @@ class V3Controller extends Controller
/** /**
* @param RecordView[] $recordViews * @param RecordView[] $recordViews
* @param \caption_record[] $captions * @param caption_record[] $captions
* @param bool[] $canSeeBusiness * @param bool[] $canSeeBusiness
*/ */
private function buildCaptionViews($recordViews, $captions, $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'), 'description notice' => $translator->trans('prod:mapboxgl: description notice'),
'title-map-dialog' => $translator->trans('prod:mapboxgl: title map dialog'), 'title-map-dialog' => $translator->trans('prod:mapboxgl: title map dialog'),
'create new user' => $translator->trans('prod:push: create new user'), '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) { foreach ($records as $record) {
$oldCollectionId = $record->getCollection()->get_coll_id(); $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") { if ($request->request->get("chg_coll_son") == "1") {
/** @var \record_adapter $child */ /** @var \record_adapter $child */
foreach ($record->getChildren() as $child) { foreach ($record->getChildren() as $child) {
if ($this->getAclForUser()->has_right_on_base($child->getBaseId(), \ACL::CANDELETERECORD)) { 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)); $this->getEventDispatcher()->dispatch(RecordEvents::DELETE, new DeleteEvent($record));
} else { } else {
// move to trash collection // move to trash collection
$record->move_to_collection($trashCollectionsBySbasId[$sbasId], $this->getApplicationBox()); $record->move_to_collection($trashCollectionsBySbasId[$sbasId]);
// disable permalinks // disable permalinks
foreach($record->get_subdefs() as $subdef) { foreach($record->get_subdefs() as $subdef) {
if( ($pl = $subdef->get_permalink()) ) { 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\Application\Helper\SubDefinitionSubstituerAware;
use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Controller\RecordsRequest; 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\RecordEvents;
use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent; use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Exception\RuntimeException;
use Alchemy\Phrasea\Metadata\PhraseanetMetadataReader; use Alchemy\Phrasea\Metadata\PhraseanetMetadataReader;
use Alchemy\Phrasea\Metadata\PhraseanetMetadataSetter; use Alchemy\Phrasea\Metadata\PhraseanetMetadataSetter;
@@ -24,7 +26,6 @@ use Alchemy\Phrasea\Record\RecordWasRotated;
use DataURI\Parser; use DataURI\Parser;
use MediaAlchemyst\Alchemyst; use MediaAlchemyst\Alchemyst;
use MediaVorus\MediaVorus; use MediaVorus\MediaVorus;
use PHPExiftool\Reader;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
class ToolsController extends Controller class ToolsController extends Controller
@@ -45,7 +46,6 @@ class ToolsController extends Controller
if (count($records) == 1) { if (count($records) == 1) {
/** @var \record_adapter $record */ /** @var \record_adapter $record */
$record = $records->first(); $record = $records->first();
$databox = $record->getDatabox();
/**Array list of subdefs**/ /**Array list of subdefs**/
$listsubdef = array_keys($record-> get_subdefs()); $listsubdef = array_keys($record-> get_subdefs());
@@ -88,14 +88,13 @@ class ToolsController extends Controller
$metadatas = true; $metadatas = true;
} }
} }
$conf = $this->getConf();
return $this->render('prod/actions/Tools/index.html.twig', [ return $this->render('prod/actions/Tools/index.html.twig', [
'records' => $records, 'records' => $records,
'record' => $record, 'record' => $record,
'recordSubdefs' => $recordAccessibleSubdefs, 'recordSubdefs' => $recordAccessibleSubdefs,
'metadatas' => $metadatas, 'metadatas' => $metadatas,
'listsubdef' => $listsubdef 'listsubdef' => $listsubdef
]); ]);
} }
@@ -118,6 +117,7 @@ class ToolsController extends Controller
} }
foreach ($records as $record) { foreach ($records as $record) {
/** @var \media_subdef $subdef */
foreach ($record->get_subdefs() as $subdef) { foreach ($record->get_subdefs() as $subdef) {
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) { if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
continue; continue;
@@ -146,6 +146,7 @@ class ToolsController extends Controller
foreach ($selection as $record) { foreach ($selection as $record) {
$substituted = false; $substituted = false;
/** @var \media_subdef $subdef */
foreach ($record->get_subdefs() as $subdef) { foreach ($record->get_subdefs() as $subdef) {
if ($subdef->is_substituted()) { if ($subdef->is_substituted()) {
$substituted = true; $substituted = true;
@@ -362,14 +363,6 @@ class ToolsController extends Controller
return $this->app->json($return); return $this->app->json($return);
} }
/**
* @return Reader
*/
private function getExifToolReader()
{
return $this->app['exiftool.reader'];
}
/** /**
* @return Alchemyst * @return Alchemyst
*/ */
@@ -449,13 +442,45 @@ class ToolsController extends Controller
try { try {
$record->set_metadatas($metadatas); $record->set_metadatas($metadatas);
} }
catch (Exception $e) { catch (\Exception $e) {
return $this->app->json(['success' => false, 'errorMessage' => $e->getMessage()]); return $this->app->json(['success' => false, 'errorMessage' => $e->getMessage()]);
} }
return $this->app->json(['success' => true, 'errorMessage' => '']); 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) public function videoEditorAction(Request $request)
{ {
$records = RecordsRequest::fromRequest($this->app, $request, false); $records = RecordsRequest::fromRequest($this->app, $request, false);
@@ -463,6 +488,7 @@ class ToolsController extends Controller
$metadatas = false; $metadatas = false;
$record = null; $record = null;
$JSFields = []; $JSFields = [];
$videoTextTrackFields = [];
if (count($records) == 1) { if (count($records) == 1) {
/** @var \record_adapter $record */ /** @var \record_adapter $record */
@@ -480,6 +506,19 @@ class ToolsController extends Controller
'name' => $meta->get_name(), 'name' => $meta->get_name(),
'_value' => $record->getCaption([$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()) { if (!$record->isStory()) {
@@ -489,11 +528,23 @@ class ToolsController extends Controller
$conf = $this->getConf(); $conf = $this->getConf();
return $this->render('prod/actions/Tools/videoEditor.html.twig', [ return $this->render('prod/actions/Tools/videoEditor.html.twig', [
'records' => $records, 'records' => $records,
'record' => $record, 'record' => $record,
'videoEditorConfig' => $conf->get(['video-editor']), 'videoEditorConfig' => $conf->get(['video-editor']),
'metadatas' => $metadatas, 'metadatas' => $metadatas,
'JSonFields' => json_encode($JSFields), '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\Controller\Controller;
use Alchemy\Phrasea\Core\Event\Thesaurus as ThesaurusEvent; use Alchemy\Phrasea\Core\Event\Thesaurus as ThesaurusEvent;
use Alchemy\Phrasea\Core\Event\Thesaurus\ThesaurusEvents; 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 Doctrine\DBAL\Driver\Connection;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; 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 * @param Request $request
* @return Response * @return Response
@@ -3031,4 +3054,12 @@ class ThesaurusController extends Controller
{ {
return $this->app['locales.available']; 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; namespace Alchemy\Phrasea\ControllerProvider\Api;
use Alchemy\Phrasea\Application as PhraseaApplication; 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 Alchemy\Phrasea\Core\Event\Listener\OAuthListener;
use Silex\Application; use Silex\Application;
use Silex\ControllerCollection; use Silex\ControllerCollection;
use Silex\ControllerProviderInterface; use Silex\ControllerProviderInterface;
use Silex\ServiceProviderInterface; use Silex\ServiceProviderInterface;
class V3 extends Api implements ControllerProviderInterface, ServiceProviderInterface class V3 extends Api implements ControllerProviderInterface, ServiceProviderInterface
{ {
const VERSION = '3.0.0'; const VERSION = '3.0.0';
public function register(Application $app) public function register(Application $app)
{ {
$app['controller.api.v3'] = $app->share(function (PhraseaApplication $app) { $app['controller.api.v3.resulthelpers'] = $app->share(function (PhraseaApplication $app) {
return (new V3Controller($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->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') ->before('controller.api.v1:ensureCanAccessToRecord')
->assert('databox_id', '\d+') ->assert('databox_id', '\d+')
->assert('record_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; return $controllers;
} }

View File

@@ -72,6 +72,9 @@ class Tools implements ControllerProviderInterface, ServiceProviderInterface
$controllers->post('/metadata/save/', 'controller.prod.tools:saveMetasAction') $controllers->post('/metadata/save/', 'controller.prod.tools:saveMetasAction')
->bind('prod_tools_metadata_save'); ->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'); $controllers->get('/videoEditor', 'controller.prod.tools:videoEditorAction');
return $controllers; return $controllers;

View File

@@ -60,6 +60,7 @@ class Thesaurus implements ControllerProviderInterface, ServiceProviderInterface
$controllers->match('newterm.php', 'controller.thesaurus:newTerm'); $controllers->match('newterm.php', 'controller.thesaurus:newTerm');
$controllers->match('properties.php', 'controller.thesaurus:properties'); $controllers->match('properties.php', 'controller.thesaurus:properties');
$controllers->match('thesaurus.php', 'controller.thesaurus:thesaurus')->bind('thesaurus_thesaurus'); $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/accept.x.php', 'controller.thesaurus:acceptXml');
$controllers->match('xmlhttp/acceptcandidates.x.php', 'controller.thesaurus:acceptCandidatesXml'); $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\RecordEvents;
use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreatedEvent; use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreatedEvent;
use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreationFailedEvent; use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreationFailedEvent;
use Alchemy\Phrasea\Core\Event\Record\SubDefinitionsCreatedEvent;
use Alchemy\Phrasea\Model\Entities\WebhookEvent; use Alchemy\Phrasea\Model\Entities\WebhookEvent;
use Silex\Application; use Silex\Application;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -30,7 +29,8 @@ class WebhookSubdefEventSubscriber implements EventSubscriberInterface
$this->app['manipulator.webhook-event']->create( $this->app['manipulator.webhook-event']->create(
WebhookEvent::RECORD_SUBDEF_CREATED, WebhookEvent::RECORD_SUBDEF_CREATED,
WebhookEvent::RECORD_SUBDEF_TYPE, WebhookEvent::RECORD_SUBDEF_TYPE,
$eventData $eventData,
[$event->getRecord()->getBaseId()]
); );
} }
@@ -45,22 +45,8 @@ class WebhookSubdefEventSubscriber implements EventSubscriberInterface
$this->app['manipulator.webhook-event']->create( $this->app['manipulator.webhook-event']->create(
WebhookEvent::RECORD_SUBDEF_FAILED, WebhookEvent::RECORD_SUBDEF_FAILED,
WebhookEvent::RECORD_SUBDEF_TYPE, WebhookEvent::RECORD_SUBDEF_TYPE,
$eventData $eventData,
); [$event->getRecord()->getBaseId()]
}
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
); );
} }
@@ -68,7 +54,6 @@ class WebhookSubdefEventSubscriber implements EventSubscriberInterface
{ {
return [ return [
RecordEvents::SUB_DEFINITION_CREATED => 'onSubdefCreated', RecordEvents::SUB_DEFINITION_CREATED => 'onSubdefCreated',
RecordEvents::SUB_DEFINITIONS_CREATED => 'onSubdefsCreated',
RecordEvents::SUB_DEFINITION_CREATION_FAILED => 'onSubdefCreationFailed' RecordEvents::SUB_DEFINITION_CREATION_FAILED => 'onSubdefCreationFailed'
]; ];
} }

View File

@@ -54,6 +54,8 @@ final class PhraseaEvents
const RECORD_EDIT = 'record.edit'; const RECORD_EDIT = 'record.edit';
const RECORD_UPLOAD = 'record.upload'; const RECORD_UPLOAD = 'record.upload';
const RECORD_AUTO_SUBTITLE = 'record.auto-subtitle';
const THESAURUS_IMPORTED = 'thesaurus.imported'; const THESAURUS_IMPORTED = 'thesaurus.imported';
const THESAURUS_FIELD_LINKED = 'thesaurus.field-linked'; const THESAURUS_FIELD_LINKED = 'thesaurus.field-linked';
const THESAURUS_CANDIDATE_ACCEPTED_AS_CONCEPT = 'thesaurus.candidate-accepted-as-concept'; 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) { $app['repo.worker-running-job'] = $app->share(function (PhraseaApplication $app) {
return $app['orm.em']->getRepository('Phraseanet:WorkerRunningJob'); 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) { $app['repo.worker-running-populate'] = $app->share(function (PhraseaApplication $app) {
return $app['orm.em']->getRepository('Phraseanet:WorkerRunningPopulate'); return $app['orm.em']->getRepository('Phraseanet:WorkerRunningPopulate');
}); });

View File

@@ -16,8 +16,7 @@ class Version
/** /**
* @var string * @var string
*/ */
private $number = '4.1.1';
private $number = '4.1.0-alpha.29a';
/** /**
* @var string * @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 FINISHED = 'finished';
const RUNNING = 'running'; const RUNNING = 'running';
const MAX_RESULT = 500;
/** /**
* @ORM\Column(type="integer") * @ORM\Column(type="integer")
* @ORM\Id * @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), 'created_on' => $permalink->get_created_on()->format(DATE_ATOM),
'id' => $permalink->get_id(), 'id' => $permalink->get_id(),
'is_activated' => $permalink->get_is_activated(), 'is_activated' => $permalink->get_is_activated(),
/** @Ignore */
'label' => $permalink->get_label(), 'label' => $permalink->get_label(),
'updated_on' => $permalink->get_last_modified()->format(DATE_ATOM), 'updated_on' => $permalink->get_last_modified()->format(DATE_ATOM),
'page_url' => $permalink->get_page(), 'page_url' => $permalink->get_page(),

View File

@@ -11,13 +11,10 @@
namespace Alchemy\Phrasea\SearchEngine\Elastic; 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 appbox;
use DateTime; use DateTime;
use igorw; use Exception;
class RecordHelper class RecordHelper
{ {
@@ -100,31 +97,31 @@ class RecordHelper
$a = explode(';', preg_replace('/\D+/', ';', trim($value))); $a = explode(';', preg_replace('/\D+/', ';', trim($value)));
switch (count($a)) { switch (count($a)) {
case 1: // yyyy 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'); $v_fix = $date->format('Y');
break; break;
case 2: // yyyy;mm 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'); $v_fix = $date->format('Y-m');
break; break;
case 3: // yyyy;mm;dd 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'); $v_fix = $date->format('Y-m-d');
break; break;
case 4: 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'); $v_fix = $date->format('Y-m-d H:i:s');
break; break;
case 5: 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'); $v_fix = $date->format('Y-m-d H:i:s');
break; break;
case 6: 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'); $v_fix = $date->format('Y-m-d H:i:s');
break; break;
} }
} catch (\Exception $e) { } catch (Exception $e) {
// no-op, v_fix = null // no-op, v_fix = null
} }
@@ -151,8 +148,16 @@ class RecordHelper
return (bool) $value; return (bool) $value;
case FieldMapping::TYPE_STRING: case FieldMapping::TYPE_STRING:
$value = substr($value, 0, 32766); // for lucene limit, before a better solution $value = str_replace("\0", '', $value); // no null char for lucene !
return str_replace("\0", '', $value); 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: default:
return $value; return $value;

View File

@@ -74,7 +74,6 @@ class RecordMoverJob extends AbstractJob
private function processData(Application $app, $row, $logsql) private function processData(Application $app, $row, $logsql)
{ {
/** @var databox $databox */
$databox = $app->findDataboxById($row['sbas_id']); $databox = $app->findDataboxById($row['sbas_id']);
$rec = $databox->get_record($row['record_id']); $rec = $databox->get_record($row['record_id']);
@@ -83,7 +82,7 @@ class RecordMoverJob extends AbstractJob
// change collection ? // change collection ?
if (array_key_exists('coll', $row)) { if (array_key_exists('coll', $row)) {
$coll = \collection::getByCollectionId($app, $databox, $row['coll']); $coll = \collection::getByCollectionId($app, $databox, $row['coll']);
$rec->move_to_collection($coll, $app['phraseanet.appbox']); $rec->move_to_collection($coll);
if ($logsql) { 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())); $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') $this->setDescription('Listen queues define on configuration, launch corresponding service for execution')
->addOption('preserve-payload', 'p', InputOption::VALUE_NONE, 'Preserve temporary payload file') ->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('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('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('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('clear-metadatas', '', InputOption::VALUE_NONE, 'Remove metadatas from documents if not compliant with Database structure (use only for write metadata service)')
->setHelp(''); ->setHelp('');
return $this; return $this;
@@ -33,9 +33,6 @@ class WorkerExecuteCommand extends Command
protected function doExecute(InputInterface $input, OutputInterface $output) protected function doExecute(InputInterface $input, OutputInterface $output)
{ {
$MWG = false;
$clearMetadatas = false;
$argQueueName = $input->getOption('queue-name'); $argQueueName = $input->getOption('queue-name');
$maxProcesses = intval($input->getOption('max-processes')); $maxProcesses = intval($input->getOption('max-processes'));
@@ -64,14 +61,6 @@ class WorkerExecuteCommand extends Command
$workerInvoker->setMaxProcessPoolValue($maxProcesses); $workerInvoker->setMaxProcessPoolValue($maxProcesses);
} }
if ($input->getOption('MWG')) {
$MWG = true;
}
if ($input->getOption('clear-metadatas')) {
$clearMetadatas = true;
}
if ($input->getOption('preserve-payload')) { if ($input->getOption('preserve-payload')) {
$workerInvoker->preservePayloads(); $workerInvoker->preservePayloads();
} }

View File

@@ -79,11 +79,11 @@ class AdminConfigurationController extends Controller
$reload = ($request->query->get('reload')) == 1 ? true : false ; $reload = ($request->query->get('reload')) == 1 ? true : false ;
if ($request->query->get('running') == 1 && $request->query->get('finished') == 1) { 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) { } 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) { } 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', [ 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) public function truncateTableAction(PhraseaApplication $app, Request $request)
{ {
/** @var WorkerRunningJobRepository $repoWorker */ /** @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\DeleteRecordWorker;
use Alchemy\Phrasea\WorkerManager\Worker\ExportMailWorker; use Alchemy\Phrasea\WorkerManager\Worker\ExportMailWorker;
use Alchemy\Phrasea\WorkerManager\Worker\Factory\CallableWorkerFactory; use Alchemy\Phrasea\WorkerManager\Worker\Factory\CallableWorkerFactory;
use Alchemy\Phrasea\WorkerManager\Worker\MainQueueWorker;
use Alchemy\Phrasea\WorkerManager\Worker\PopulateIndexWorker; use Alchemy\Phrasea\WorkerManager\Worker\PopulateIndexWorker;
use Alchemy\Phrasea\WorkerManager\Worker\ProcessPool; use Alchemy\Phrasea\WorkerManager\Worker\ProcessPool;
use Alchemy\Phrasea\WorkerManager\Worker\PullAssetsWorker; use Alchemy\Phrasea\WorkerManager\Worker\PullAssetsWorker;
use Alchemy\Phrasea\WorkerManager\Worker\Resolver\TypeBasedWorkerResolver; use Alchemy\Phrasea\WorkerManager\Worker\Resolver\TypeBasedWorkerResolver;
use Alchemy\Phrasea\WorkerManager\Worker\SubdefCreationWorker; use Alchemy\Phrasea\WorkerManager\Worker\SubdefCreationWorker;
use Alchemy\Phrasea\WorkerManager\Worker\SubtitleWorker;
use Alchemy\Phrasea\WorkerManager\Worker\WebhookWorker; use Alchemy\Phrasea\WorkerManager\Worker\WebhookWorker;
use Alchemy\Phrasea\WorkerManager\Worker\WorkerInvoker; use Alchemy\Phrasea\WorkerManager\Worker\WorkerInvoker;
use Alchemy\Phrasea\WorkerManager\Worker\WriteMetadatasWorker; use Alchemy\Phrasea\WorkerManager\Worker\WriteMetadatasWorker;
@@ -128,6 +130,16 @@ class AlchemyWorkerServiceProvider implements PluginProviderInterface
return (new DeleteRecordWorker()) return (new DeleteRecordWorker())
->setApplicationBox($app['phraseanet.appbox']); ->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') ->method('GET|POST')
->bind('worker_admin_pullAssets'); ->bind('worker_admin_pullAssets');
$controllers->match('/queue-monitor', 'controller.worker.admin.configuration:queueMonitorAction')
->method('GET')
->bind('worker_admin_queue_monitor');
return $controllers; 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\ExportSubscriber;
use Alchemy\Phrasea\WorkerManager\Subscriber\RecordSubscriber; use Alchemy\Phrasea\WorkerManager\Subscriber\RecordSubscriber;
use Alchemy\Phrasea\WorkerManager\Subscriber\SearchengineSubscriber; use Alchemy\Phrasea\WorkerManager\Subscriber\SearchengineSubscriber;
use Alchemy\Phrasea\WorkerManager\Subscriber\SubtitleSubscriber;
use Alchemy\Phrasea\WorkerManager\Subscriber\WebhookSubscriber; use Alchemy\Phrasea\WorkerManager\Subscriber\WebhookSubscriber;
use Silex\Application; use Silex\Application;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; 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 AssetsIngestSubscriber($app['alchemy_worker.message.publisher']));
$dispatcher->addSubscriber(new SearchengineSubscriber($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 WebhookSubscriber($app['alchemy_worker.message.publisher']));
$dispatcher->addSubscriber(new SubtitleSubscriber(new LazyLocator($app, 'repo.worker-job'), $app['alchemy_worker.message.publisher']));
return $dispatcher; return $dispatcher;
}) })

View File

@@ -29,7 +29,9 @@ class AMQPConnection
MessagePublisher::CREATE_RECORD_TYPE => MessagePublisher::CREATE_RECORD_QUEUE, MessagePublisher::CREATE_RECORD_TYPE => MessagePublisher::CREATE_RECORD_QUEUE,
MessagePublisher::PULL_QUEUE => MessagePublisher::PULL_QUEUE, MessagePublisher::PULL_QUEUE => MessagePublisher::PULL_QUEUE,
MessagePublisher::POPULATE_INDEX_TYPE => MessagePublisher::POPULATE_INDEX_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 // the corresponding worker queues and retry queues, loop queue
@@ -44,7 +46,6 @@ class AMQPConnection
MessagePublisher::PULL_QUEUE => MessagePublisher::LOOP_PULL_QUEUE MessagePublisher::PULL_QUEUE => MessagePublisher::LOOP_PULL_QUEUE
]; ];
// default message TTL in retry queue in millisecond
public static $defaultFailedQueues = [ public static $defaultFailedQueues = [
MessagePublisher::WRITE_METADATAS_TYPE => MessagePublisher::FAILED_METADATAS_QUEUE, MessagePublisher::WRITE_METADATAS_TYPE => MessagePublisher::FAILED_METADATAS_QUEUE,
MessagePublisher::SUBDEF_CREATION_TYPE => MessagePublisher::FAILED_SUBDEF_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() public function connectionClose()
{ {
$this->channel->close(); $this->channel->close();

View File

@@ -18,6 +18,12 @@ class MessagePublisher
const WEBHOOK_TYPE = 'webhook'; const WEBHOOK_TYPE = 'webhook';
const POPULATE_INDEX_TYPE = 'populateIndex'; const POPULATE_INDEX_TYPE = 'populateIndex';
const PULL_ASSETS_TYPE = 'pullAssets'; 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 // worker queue to be consumed, when no ack , it is requeued to the retry queue
const EXPORT_QUEUE = 'export-queue'; const EXPORT_QUEUE = 'export-queue';

View File

@@ -54,18 +54,20 @@ class RecordSubscriber implements EventSubscriberInterface
if (!$record->isStory()) { if (!$record->isStory()) {
$subdefs = $record->getDatabox()->get_subdef_structure()->getSubdefGroup($record->getType()); $subdefs = $record->getDatabox()->get_subdef_structure()->getSubdefGroup($record->getType());
foreach ($subdefs as $subdef) { if ($subdefs !== null) {
$payload = [ foreach ($subdefs as $subdef) {
'message_type' => MessagePublisher::SUBDEF_CREATION_TYPE, $payload = [
'payload' => [ 'message_type' => MessagePublisher::SUBDEF_CREATION_TYPE,
'recordId' => $event->getRecord()->getRecordId(), 'payload' => [
'databoxId' => $event->getRecord()->getDataboxId(), 'recordId' => $event->getRecord()->getRecordId(),
'subdefName' => $subdef->get_name(), 'databoxId' => $event->getRecord()->getDataboxId(),
'status' => $event->isNewRecord() ? MessagePublisher::NEW_RECORD_MESSAGE : '' '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 class ProcessPool implements LoggerAwareInterface
{ {
const MAX_PROCESSES = 4; const MAX_PROCESSES = 1;
/** /**
* @var int * @var int
@@ -41,7 +41,7 @@ class ProcessPool implements LoggerAwareInterface
* Sets a logger instance on the object * Sets a logger instance on the object
* *
* @param LoggerInterface $logger * @param LoggerInterface $logger
* @return null * @return void
*/ */
public function setLogger(LoggerInterface $logger) 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 collection $collection
* @param appbox $appbox * @param appbox $appbox WTF this parm is useless
* @return record_adapter * @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()) { if ($this->getCollection()->get_base_id() === $collection->get_base_id()) {
return $this; 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 === '') { if ($tmp_val === '') {
$caption_field_value->delete(); $caption_field_value->delete();
@@ -1068,8 +1072,11 @@ class record_adapter implements RecordInterface, cache_cacheableInterface
$caption_field_value->setVocab($vocab, $vocab_id); $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; return $this;

View File

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

View File

@@ -119,7 +119,7 @@
<vcodec>libvpx</vcodec> <vcodec>libvpx</vcodec>
</subdef> </subdef>
<subdef class="preview" name="audiovideowav" downloadable="true" orderable="true" presets="Wave Mono 8 kHz"> <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> <meta>no</meta>
<mediatype>audio</mediatype> <mediatype>audio</mediatype>
<audiobitrate>128</audiobitrate> <audiobitrate>128</audiobitrate>
@@ -131,7 +131,7 @@
<label lang="en">Audio WAVE 8 kHz</label> <label lang="en">Audio WAVE 8 kHz</label>
</subdef> </subdef>
<subdef class="preview" name="audiovideomp3" downloadable="true" orderable="true" presets="Normal MP3 128 kbit/s"> <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> <meta>no</meta>
<mediatype>audio</mediatype> <mediatype>audio</mediatype>
<audiobitrate>180</audiobitrate> <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", "normalize-css": "^2.1.0",
"npm": "^6.0.0", "npm": "^6.0.0",
"npm-modernizr": "^2.8.3", "npm-modernizr": "^2.8.3",
"phraseanet-production-client": "0.34.214-d", "phraseanet-production-client": "0.34.256-d",
"requirejs": "^2.3.5", "requirejs": "^2.3.5",
"tinymce": "^4.0.28", "tinymce": "^4.0.28",
"underscore": "^1.8.3", "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"?> <?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"> <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> <header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/> <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> <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"?> <?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"> <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> <header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/> <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> <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"?> <?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"> <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> <header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/> <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> <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"?> <?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"> <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> <header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/> <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> <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; overflow: scroll;
} }
.display_id { .display_id {
top: 5px; top: 0;
margin: 0 0 0 5px; margin: 0 0 0 5px;
} }
} }
@@ -134,8 +134,8 @@ img {
background-color: #212121; background-color: #212121;
} }
.display_id { .display_id {
top: 4px; top: 5px;
left: 8px; left: 5px;
} }
.agreement { .agreement {
position: absolute; position: absolute;
@@ -173,7 +173,9 @@ img {
.display_id { .display_id {
background-color: #FFFFFF; background-color: #FFFFFF;
padding: 3px 6px; padding: 3.5px 6px;
min-width: 15px;
text-align: center;
font-weight: bold; font-weight: bold;
z-index: 99; z-index: 99;
color: #212121; color: #212121;
@@ -214,6 +216,28 @@ img {
overflow: hidden; overflow: hidden;
background-color: #1F1E1B; background-color: #1F1E1B;
color: #BFBFBF; 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 { #basket_options {
@@ -230,6 +254,11 @@ img {
#basket_infos .user_infos { #basket_infos .user_infos {
height: 120px; height: 120px;
top: auto; top: auto;
bottom: 43px;
}
.lightbox-icon {
width: 10px;
padding-bottom: 1px;
} }
#basket_infos { #basket_infos {
@@ -284,7 +313,7 @@ img {
} }
#right_column .right_column_wrapper { #right_column .right_column_wrapper {
//top: 30px; top: 30px;
bottom: 45px; bottom: 45px;
} }
@@ -296,6 +325,7 @@ img {
#record_infos { #record_infos {
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
margin-bottom: 80px;
} }
#record_compare { #record_compare {
@@ -303,7 +333,10 @@ img {
top: auto; top: auto;
left: auto; left: auto;
} }
.download-feed{
color: #fff;
font-size: 22px;
}
#record_compare .header, #record_compare .lightbox_container { #record_compare .header, #record_compare .lightbox_container {
left: 5px; left: 5px;
} }
@@ -546,10 +579,14 @@ table th i {
} }
.basket_downloader { .basket_downloader {
background-color: #1F1E1B; background-color: #ffffff;
margin: 0 10px; margin: 0 10px;
padding: 2px; padding: 4px 0;
border: 0; border: 0;
transition: 0.3s all;
&:hover {
background-color: #e6e6e6;
}
} }
hr { hr {
@@ -562,6 +599,13 @@ hr {
.report { .report {
margin: 0 10px; margin: 0 10px;
vertical-align: bottom; 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**/ /** 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 { BODY, TD, INPUT {
FONT-FAMILY: Verdana, Arial, helvetica, sans-serif; FONT-FAMILY: Verdana, Arial, helvetica, sans-serif;
font-size: 12px; font-size: 12px;
@@ -8,11 +45,11 @@ FORM {
margin: 0px; margin: 0px;
} }
BODY.dialog { .dialog {
background-color: ButtonFace; background-color: ButtonFace;
} }
BODY.dialog .x3Dbox { .x3Dbox {
position: relative; position: relative;
top: 0px; top: 0px;
left: 0px; left: 0px;
@@ -24,7 +61,7 @@ BODY.dialog .x3Dbox {
border-right: 1px solid #ffffff; border-right: 1px solid #ffffff;
} }
BODY.dialog .x3Dbox .title { .x3Dbox .title {
position: absolute; position: absolute;
top: -9px; top: -9px;
left: 20px; left: 20px;
@@ -58,7 +95,7 @@ H3, H4 {
display: inline; display: inline;
} }
DIV.thbox { .thbox {
BACKGROUND-COLOR: #ffffff; BACKGROUND-COLOR: #ffffff;
border-top: 1px solid #d0d0d0; border-top: 1px solid #d0d0d0;
border-left: 1px solid #d0d0d0; border-left: 1px solid #d0d0d0;
@@ -69,7 +106,7 @@ DIV.thbox {
z-index: 1; z-index: 1;
} }
DIV.thbox .onglet { .thbox .onglet {
position: absolute; position: absolute;
top: -22px; top: -22px;
left: 5px; left: 5px;
@@ -87,20 +124,20 @@ DIV.thbox .onglet {
z-index: 2; z-index: 2;
} }
DIV.thbox HR { .thbox HR {
COLOR: #d0d0d0; COLOR: #d0d0d0;
height: 1px; height: 1px;
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
} }
DIV.glossaire { .glossaire {
position: relative; position: relative;
left: 4px; left: 4px;
white-space: nowrap; white-space: nowrap;
} }
DIV.glossaire U { .glossaire U {
position: relative; position: relative;
left: 1px; left: 1px;
TEXT-DECORATION: none; TEXT-DECORATION: none;
@@ -116,30 +153,30 @@ DIV.glossaire U {
cursor: pointer; cursor: pointer;
} }
DIV.glossaire U.nots { .glossaire U.nots {
border: 1px solid #F0F0F0; border: 1px solid #F0F0F0;
BACKGROUND-COLOR: #F0F0F0; BACKGROUND-COLOR: #F0F0F0;
cursor: default; cursor: default;
} }
DIV.glossaire DIV.S_ { .glossaire .S_ {
cursor: pointer; cursor: pointer;
BACKGROUND-COLOR: #99a2d0; BACKGROUND-COLOR: #99a2d0;
COLOR: #FFFFFF; COLOR: #FFFFFF;
} }
DIV.glossaire DIV.s_ { .glossaire .s_ {
cursor: pointer; cursor: pointer;
} }
DIV.glossaire DIV.r0_ { .glossaire .r0_ {
} }
DIV.glossaire DIV.r1_ { .glossaire .r1_ {
COLOR: #FF4000; COLOR: #FF4000;
} }
DIV.glossaire DIV.OB { .glossaire .OB {
position: relative; position: relative;
top: 0px; top: 0px;
padding-top: 0px; padding-top: 0px;
@@ -149,11 +186,11 @@ DIV.glossaire DIV.OB {
border-bottom: 1px solid #cccccc; border-bottom: 1px solid #cccccc;
} }
DIV.glossaire DIV.ob { .glossaire .ob {
display: none; display: none;
} }
DIV.glossaire DIV.hb { .glossaire .hb {
position: relative; position: relative;
top: 0px; top: 0px;
padding-top: 0px; padding-top: 0px;
@@ -163,46 +200,46 @@ DIV.glossaire DIV.hb {
border-bottom: 1px solid #cccccc; border-bottom: 1px solid #cccccc;
} }
DIV.glossaire DIV.ctroot { .glossaire .ctroot {
position: relative; position: relative;
top: 0px; top: 0px;
padding: 0px; padding: 0px;
border: none; 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; margin: 2px;
border: 1px solid #000000; border: 1px solid #000000;
background-color: #ffffff; background-color: #ffffff;
} }
DIV.tableContainerDragOver { .tableContainerDragOver {
margin: 0px; margin: 0px;
border: 3px solid #99a2d0; border: 3px solid #99a2d0;
} }
DIV.tableContainer DIV.tbody { .tableContainer .tbody {
position: relative; position: relative;
overflow: auto; overflow: auto;
} }
DIV.tableContainer TABLE { .tableContainer TABLE {
table-layout: fixed; table-layout: fixed;
} }
DIV.tableContainer THEAD TH { .tableContainer THEAD TH {
background: #e0ece8; background: #e0ece8;
border-left: 1px solid #ffffff; border-left: 1px solid #ffffff;
border-right: 1px solid #c0ccc8; border-right: 1px solid #c0ccc8;
@@ -216,15 +253,15 @@ DIV.tableContainer THEAD TH {
margin: 0px; margin: 0px;
} }
DIV.tableContainer TBODY TR.s_ { .tableContainer TBODY TR.s_ {
} }
DIV.tableContainer TBODY TR.S_ { .tableContainer TBODY TR.S_ {
BACKGROUND-COLOR: #99a2d0; BACKGROUND-COLOR: #99a2d0;
COLOR: #FFFFFF; COLOR: #FFFFFF;
} }
DIV.tableContainer TBODY TD { .tableContainer TBODY TD {
border-left: 1px solid #ffffff; border-left: 1px solid #ffffff;
border-right: 1px solid #cccccc; border-right: 1px solid #cccccc;
font-weight: normal; font-weight: normal;
@@ -235,77 +272,68 @@ DIV.tableContainer TBODY TD {
margin: 0px; margin: 0px;
} }
DIV.menu { .menu {
FONT-FAMILY: Arial, helvetica, sans-serif; FONT-FAMILY: Arial, helvetica, sans-serif;
font-size: 12px; 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; padding: 0px;
margin: 0px; margin: 0px;
visibility: hidden; visibility: hidden;
position: absolute; position: absolute;
top: 0px; top: 0;
left: 0px; left: 0;
background-color: #d4d0c8; background-color: #535353;
} }
DIV.menu IMG { .menu IMG {
padding: 0px; padding-right: 2px;
margin: 0px; margin: 0;
position: relative; position: relative;
left: -10px;
top: 2px;
} }
DIV.menu A { .menu p {
padding: 2px;
position: relative;
}
.menu a {
font-size: 12px; font-size: 12px;
display: block; display: block;
position: relative; position: relative;
text-decoration: none; text-decoration: none;
color: #000000; color: #fff;
padding-top: 1px; padding: 5px 6px;
padding-bottom: 1px; background-color: #535353;
padding-left: 13px;
padding-right: 3px;
overflow: hidden;
border: none 0px #FFFFFF;
white-space: nowrap;
} }
DIV.menu A:hover { .menu A:hover {
font-size: 12px; font-size: 12px;
display: block; display: block;
position: relative; position: relative;
text-decoration: none; text-decoration: none;
color: #ffffff; color: #ffffff;
background-color: #000080; background-color: #884c92;
} }
DIV.menu A.disabled { .menu A.disabled {
font-size: 12px; font-size: 12px;
display: block; display: block;
position: relative; position: relative;
text-decoration: none; text-decoration: none;
color: #A0A0A0; color: lighten(#535353, 10%);
padding-top: 1px;
padding-bottom: 1px;
padding-left: 13px;
padding-right: 3px;
overflow: hidden; overflow: hidden;
} }
DIV.menu A.disabled:hover { .menu A.disabled:hover {
font-size: 12px; font-size: 12px;
display: block; display: block;
position: relative; position: relative;
text-decoration: none; text-decoration: none;
color: #A0A0A0; color: lighten(#535353, 5%);
background-color: #d4d0c8; background-color: #535353;
} }
DIV.menu .line { .menu .line {
display: block; display: block;
position: relative; position: relative;
height: 0px; height: 0px;
@@ -316,3 +344,90 @@ DIV.menu .line {
border-top: 1px solid #555555; border-top: 1px solid #555555;
border-bottom: 1px solid #ffffff; 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 }} {{ 'admin::workermanager:tab:metadata: title' |trans }}
</a> </a>
</li> </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> </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-pull-assets"></div>
<div role="tabpanel" class="tab-pane fade" id="worker-subview"></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-metadata"></div>
<div role="tabpanel" class="tab-pane fade" id="worker-queue-monitor"></div>
</div> </div>
</div> </div>

View File

@@ -42,7 +42,7 @@
<table class="admintable"> <table class="admintable">
<thead> <thead>
<tr> <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: record_id' | trans }}</th>
<th class="sortable">{{ 'admin::workermanager:tab:workerinfo: work' | trans }}</th> <th class="sortable">{{ 'admin::workermanager:tab:workerinfo: work' | trans }}</th>
<th class="sortable">{{ 'admin::workermanager:tab:workerinfo: work_on' | trans }}</th> <th class="sortable">{{ 'admin::workermanager:tab:workerinfo: work_on' | trans }}</th>
@@ -57,7 +57,7 @@
{% for workerRow in workerRunningJob | sort | reverse %} {% for workerRow in workerRunningJob | sort | reverse %}
<tr> <tr>
<td>{{ workerRow.databoxId }}</td> <td>{{ workerRow.databoxId | sbas_labels(app) }}</td>
<td>{{ workerRow.recordId }}</td> <td>{{ workerRow.recordId }}</td>
<td>{{ workerRow.getWorkName }}</td> <td>{{ workerRow.getWorkName }}</td>
<td>{{ workerRow.workOn }}</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> </div>
{% endif %} {% endif %}
{{ _self.choose_title('download', choose_export_title, default_export_title) }} {{ _self.choose_title('download', choose_export_title, default_export_title) }}
{% if app['conf'].get(['registry', 'actions', 'tou-validation-required-for-export']) == true %} {% 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"> <label for="TOU_acceptDL" class="checkbox">
<input type="checkbox" name="TOU_accept" id="TOU_acceptDL" value="1" /> <input type="checkbox" name="TOU_accept" id="TOU_acceptDL" value="1" />
{% set beginning_link = '<a href="' ~ path('get_tou') ~ '" class="TOUview">' %} {% 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="lst" value="{{lst}}"/>
<input type="hidden" name="ssttid" value="{{ssttid}}"/> <input type="hidden" name="ssttid" value="{{ssttid}}"/>
<div> <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"> <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() %} {% set my_email = app.getAuthenticatedUser().getEmail() %}
{% if my_email != '' %} <div class="acceptDl-info">
<label class="checkbox"> {% if my_email != '' %}
<input type="checkbox" name="reading_confirm" value="1" /> <label class="checkbox">
{% trans with {'%my_email%' : my_email} %}Recevoir un accuse de reception a %my_email%{% endtrans %} <input type="checkbox" name="reading_confirm" value="1" />
</label> {% trans with {'%my_email%' : my_email} %}Recevoir un accuse de reception a %my_email%{% endtrans %}
{% else %} </label>
<label class="checkbox"> {% else %}
{{ 'Accuse de reception indisponible, vous n\'avez pas declare d\'adresse email' | trans }} <label class="checkbox">
<input type="checkbox" name="reading_confirm" value="1" readonly /> {{ 'Accuse de reception indisponible, vous n\'avez pas declare d\'adresse email' | trans }}
</label> <input type="checkbox" name="reading_confirm" value="1" readonly />
{% endif %} </label>
<span style="font-style: italic; color: #999999;">{{ 'Entrez plusieurs adresses email en les separant par des points-virgules' | trans }}</span> {% endif %}
<span style="font-style: italic; color: #999999;">{{ 'export:email:: acknowledgement info' | trans }}</span>
</div>
</div> </div>
<div> <div>
<p>{{ 'export::mail: contenu du mail' | trans }}</p> <p>{{ 'export::mail: contenu du mail' | trans }}</p>
@@ -235,7 +240,7 @@
{{ _self.choose_title('sendmail', choose_export_title, default_export_title) }} {{ _self.choose_title('sendmail', choose_export_title, default_export_title) }}
{% if app['conf'].get(['registry', 'actions', 'tou-validation-required-for-export']) == true %} {% 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"> <label for="TOU_acceptMail" class="checkbox">
<input type="checkbox" name="TOU_accept" id="TOU_acceptMail" value="1" /> <input type="checkbox" name="TOU_accept" id="TOU_acceptMail" value="1" />
{% set beginning_link = '<a href="' ~ path('get_tou') ~ '" class="TOUview">' %} {% set beginning_link = '<a href="' ~ path('get_tou') ~ '" class="TOUview">' %}
@@ -388,7 +393,7 @@
</div> </div>
{% if app['conf'].get(['registry', 'actions', 'tou-validation-required-for-export']) == true %} {% 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"> <label for="TOU_acceptOrder" class="checkbox">
<input type="checkbox" name="TOU_accept" id="TOU_acceptOrder" value="1" /> <input type="checkbox" name="TOU_accept" id="TOU_acceptOrder" value="1" />
{% set beginning_link = '<a href="' ~ path('get_tou') ~ '" class="TOUview">' %} {% set beginning_link = '<a href="' ~ path('get_tou') ~ '" class="TOUview">' %}
@@ -469,7 +474,7 @@
{% endif %} {% endif %}
{% if app['conf'].get(['registry', 'actions', 'tou-validation-required-for-export']) == true %} {% 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"> <label for="TOU_acceptFTP" class="checkbox">
<input type="checkbox" name="TOU_accept" id="TOU_acceptFTP" value="1" /> <input type="checkbox" name="TOU_accept" id="TOU_acceptFTP" value="1" />
{% set beginning_link = '<a href="' ~ path('get_tou') ~ '" class="TOUview">' %} {% set beginning_link = '<a href="' ~ path('get_tou') ~ '" class="TOUview">' %}

View File

@@ -232,7 +232,7 @@
{% endif %} {% endif %}
</li> </li>
<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> <span>
{{ 'phraseanet:: aide' | trans }} {{ 'phraseanet:: aide' | trans }}
</span> </span>

View File

@@ -47,4 +47,16 @@
</div> </div>
</div> </div>
{% endif %} {% 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> </div>

View File

@@ -92,11 +92,12 @@
<div class="PNB" style="height:30px;bottom:auto;"> <div class="PNB" style="height:30px;bottom:auto;">
<table border="0" cellspacing="0" cellpadding="0" style="width:100%;"> <table border="0" cellspacing="0" cellpadding="0" style="width:100%;">
<tr> <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>
<td style="width:40px;"> <td style="width:40px;">
<button class="ui-corner-all basket_downloader" title="{{ 'boutton::telecharger tous les documents' | trans }}"> <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> </button>
</td> </td>
<td> <td>

View File

@@ -1,25 +1,25 @@
<span style="padding:10px 4px 7px;"> <span style="padding:10px 4px 7px;">
{% if feed_element %} {% if feed_element %}
<button class="previous_button play btn btn-mini" title="{{ 'boutton::precedent' | trans }}"> <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>
<button class="previous_button pause btn btn-mini btn-inverse" title="{{ 'boutton::precedent' | trans }}"> <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>
<button class="play_button btn btn-mini" title="{{ 'boutton::demarrer' | trans }}"> <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>
<button class="pause_button btn btn-mini" title="{{ 'boutton::pause' | trans }}"> <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>
<button class="next_button play btn btn-mini" title="{{ 'boutton::suivant' | trans }}"> <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>
<button class="next_button pause btn btn-mini btn-inverse" title="{{ 'boutton::suivant' | trans }}"> <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>
<button class="download_button btn btn-mini btn-inverse" title="{{ 'boutton::telecharger' | trans }}"> <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> </button>
<form name="download_form" style="display:none;"> <form name="download_form" style="display:none;">
<input type="hidden" name="basrec" value="{{feed_element.getRecord(app).get_serialize_key()}}"/> <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"/> <link type="text/css" rel="stylesheet" href="/assets/lightbox/css/lightbox{% if not app.debug %}.min{% endif %}.css" media="screen"/>
{% endblock %} {% endblock %}
{% block icon %}
<link rel="shortcut icon" type="image/x-icon" href="/assets/lightbox/images/favicon.ico">
{% endblock %}
{% block content %} {% block content %}
<div id="main_index" style="margin-top:50px;"> <div id="main_index" style="margin-top:50px;">
<table id="main_wrapper" cellspacing="0" cellpadding="0"> <table id="main_wrapper" cellspacing="0" cellpadding="0">

View File

@@ -1,5 +1,8 @@
{% extends "common/index_bootstrap.html.twig" %} {% 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 %} {% block extra_content %}
<div id="DIALOG"></div> <div id="DIALOG"></div>
{% endblock %} {% endblock %}

View File

@@ -1,42 +1,31 @@
<span style="padding:10px 4px 7px;"> <span style="padding:10px 4px 7px;">
{% if basket_element %} {% if basket_element %}
<button class="previous_button play btn btn-mini" title="{{ 'boutton::precedent' | trans }}"> <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>
<button class="previous_button pause btn btn-mini btn-inverse" title="{{ 'boutton::precedent' | trans }}" style="display:none"> <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>
<button class="play_button btn btn-mini" title="{{ 'boutton::demarrer' | trans }}"> <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>
<button class="pause_button btn btn-mini" title="{{ 'boutton::pause' | trans }}" style="display:none"> <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>
<button class="next_button play btn btn-mini" title="{{ 'boutton::suivant' | trans }}"> <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>
<button class="next_button pause btn btn-mini btn-inverse" title="{{ 'boutton::suivant' | trans }}" style="display:none"> <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>
<button class="download_button btn btn-mini btn-inverse" title="{{ 'boutton::telecharger' | trans }}"> <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> </button>
<form name="download_form" style="display:none;"> <form name="download_form" style="display:none;">
<input type="hidden" name="basrec" value="{{basket_element.getRecord(app).get_serialize_key()}}"/> <input type="hidden" name="basrec" value="{{basket_element.getRecord(app).get_serialize_key()}}"/>
</form> </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 %} {% endif %}
</span> </span>

View File

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

View File

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

View File

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

View File

@@ -29,6 +29,16 @@
{{ "video range extractor" | trans }} {{ "video range extractor" | trans }}
</a> </a>
</li> </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> </ul>
</div> </div>
@@ -82,7 +92,9 @@
</video> </video>
</div> </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 id="thumb_camera_button"></div>
<div class="vertical-divider"></div> <div class="vertical-divider"></div>
@@ -158,6 +170,108 @@
<div id="rangeExtractor" class=""> <div id="rangeExtractor" class="">
<div class="video-range-editor-container"></div> <div class="video-range-editor-container"></div>
</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 %} {% endif %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@@ -173,7 +287,14 @@
> >
</iframe> </iframe>
</div> </div>
<style>
.video-subtitle-editor-container .number {
float: none;
}
.video-subtitle-editor-container .editing .number {
float: left;
}
</style>
<script type="text/javascript"> <script type="text/javascript">
var toolsConfig = { var toolsConfig = {
selectionLength: {{ selectionLength }}, selectionLength: {{ selectionLength }},
@@ -217,6 +338,8 @@
{% for subdef in previewHtml5 %} {% for subdef in previewHtml5 %}
{ {
ratio: '{{ ratio }}', ratio: '{{ ratio }}',
width: '{{ width }}',
height: '{{ height }}',
framerate: {{ record.exif[constant('media_subdef::TC_DATA_FRAMERATE')] | round(2) }}, framerate: {{ record.exif[constant('media_subdef::TC_DATA_FRAMERATE')] | round(2) }},
type: "{{ subdef.get_mime() }}", type: "{{ subdef.get_mime() }}",
src: "{{ subdef.get_url() }}" src: "{{ subdef.get_url() }}"
@@ -231,6 +354,28 @@
preferences: { preferences: {
overlapChapters: {% if overlapChapters != NULL %}{{ overlapChapters }}{% else %}1{% endif %}, 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> </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> <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}}" /> <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><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> <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><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> <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-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="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="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" id="bitly_loader"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(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="/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" src="{{ path('minifier', { 'f' : 'skins/thesaurus/xmlhttp.js' }) }}"></script>#}
<script type="text/javascript"> <script type="text/javascript">
function loaded() //$('.close-dialog').trigger('click');
{
window.name="ACCEPT";
self.focus();
}
function ok() function ok()
{ {
as = ""; as = "";
if((n=document.forms[0].as.length) > 0) if($(".as_1").length > 0)
{ {
for(i=0; i<n && as==""; i++) if($(".as_1")[0].checked) {
{ as = $(".as_1").val();
if(document.forms[0].as[i].checked) }
as = document.forms[0].as[i].value; if($(".as_2").length > 0 && $(".as_2")[0].checked) {
} as = $(".as_2").val();
}
} }
else else
{ {
as = document.forms[0].as.value; if($(".as_3").length > 0) {
as = $(".as_3").val();
}
} }
if(as == "TS") if(as == "TS")
{ {
@@ -59,14 +58,13 @@
switch(refresh.item(i).getAttribute("type")) switch(refresh.item(i).getAttribute("type"))
{ {
case "CT": case "CT":
{{ opener }}.reloadCtermsBranch(refresh.item(i).getAttribute("id")); reloadCtermsBranch(refresh.item(i).getAttribute("id"));
break; break;
case "TH": case "TH":
{{ opener }}.reloadThesaurusBranch(refresh.item(i).getAttribute("id")); reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
break; break;
} }
} }
self.close();
} }
else if(as == "SY") else if(as == "SY")
{ {
@@ -85,19 +83,23 @@
switch(refresh.item(i).getAttribute("type")) switch(refresh.item(i).getAttribute("type"))
{ {
case "CT": case "CT":
{{ opener }}.reloadCtermsBranch(refresh.item(i).getAttribute("id")); reloadCtermsBranch(refresh.item(i).getAttribute("id"));
break; break;
case "TH": case "TH":
{{ opener }}.reloadThesaurusBranch(refresh.item(i).getAttribute("id")); reloadThesaurusBranch(refresh.item(i).getAttribute("id"));
break; break;
} }
} }
self.close();
} }
$('.close-dialog').trigger('click');
}
function closeModal() {
$('.close-dialog').trigger('click');
} }
</script> </script>
</head> </head>
<body id="desktop" onload="loaded();" class="dialog"> <body id="desktop" class="dialog">
{% if not cterm_found %} {% if not cterm_found %}
<center> <center>
@@ -109,11 +111,9 @@
<br/> <br/>
{{ 'thesaurus:: refresh' | trans }} {{ 'thesaurus:: refresh' | trans }}
<br/> <br/>
<br/> <div class="thesaurus_confirm_bottom_block">
<br/> <input type="button" class="cancel_btn" id="cancel_button" value="{{ 'boutton::fermer' | trans }}" onclick="closeModal();">
<br/> </div>
<br/>
<input style="position:relative; z-index:2; width:100px" type="button" id="cancel_button" value="{{ 'boutton::fermer' | trans }}" onclick="self.close();">
{% else %} {% else %}
{% if not term_found %} {% if not term_found %}
<center> <center>
@@ -125,11 +125,9 @@
<br/> <br/>
{{ 'thesaurus:: refresh' | trans }} {{ 'thesaurus:: refresh' | trans }}
<br/> <br/>
<br/> <div class="thesaurus_confirm_bottom_block">
<br/> <input type="button" class="cancel_btn" id="cancel_button" value="{{ 'boutton::fermer' | trans }}" onclick="closeModal();">
<br/> </div>
<br/>
<input style="position:relative; z-index:2; width:100px" type="button" id="cancel_button" value="{{ 'boutton::fermer' | trans }}" onclick="self.close();">
{% else %} {% else %}
{% if acceptable %} {% if acceptable %}
<center> <center>
@@ -144,9 +142,9 @@
{{ 'thesaurus:: Accepter le terme comme' | trans }} {{ 'thesaurus:: Accepter le terme comme' | trans }}
<br/><br/><h4>{{ fullpath_src | raw }}</h4><br/><br/> <br/><br/><h4>{{ fullpath_src | raw }}</h4><br/><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/> <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 %} {% set fullpath_tgt_raw = fullpath_tgt | raw %}
{% trans with {'%fullpath_tgt_raw%' : fullpath_tgt_raw} %}thesaurus:: comme synonyme de %fullpath_tgt_raw%{% endtrans %} {% trans with {'%fullpath_tgt_raw%' : fullpath_tgt_raw} %}thesaurus:: comme synonyme de %fullpath_tgt_raw%{% endtrans %}
<br/> <br/>
@@ -155,13 +153,12 @@
{{ 'thesaurus:: Accepter la branche comme' | trans }} {{ 'thesaurus:: Accepter la branche comme' | trans }}
&nbsp;{{ 'thesaurus:: comme terme specifique' | trans }} &nbsp;{{ 'thesaurus:: comme terme specifique' | trans }}
<br/><br/><h4>{{ fullpath_tgt | raw }}</h4><br/><br/> <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 %} {% endif %}
<br/> <div class="thesaurus_confirm_bottom_block">
<br/> <input class="cancel_btn" type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="closeModal();">
<input style="position:relative; z-index:2; width:100px" type="button" id="ok_button" value="{{ 'boutton::valider' | trans }}" onclick="ok();"> <input class="validate_btn" type="button" id="ok_button" value="{{ 'boutton::valider' | trans }}" onclick="ok();">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </div>
<input style="position:relative; z-index:2; width:100px" type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="self.close();">
</form> </form>
</center> </center>
{% else %} {% else %}
@@ -171,13 +168,9 @@
<br/> <br/>
{% trans with {'%cfield%' : cfield} %}thesaurus:: A cet emplacement du thesaurus , un candidat du champ %cfield% ne peut etre accepte{% endtrans %} {% trans with {'%cfield%' : cfield} %}thesaurus:: A cet emplacement du thesaurus , un candidat du champ %cfield% ne peut etre accepte{% endtrans %}
<br/> <br/>
<br/> <div class="thesaurus_confirm_bottom_block">
<br/> <input class="cancel_btn" type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="closeModal();">
<br/> </div>
<br/>
<br/>
<br/>
<input style="position:relative; z-index:2; width:100px" type="button" id="cancel_button" value="{{ 'boutton::annuler' | trans }}" onclick="self.close();">
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,41 +25,55 @@
switch(button) switch(button)
{ {
case "submit": case "submit":
// document.forms[0].target="LINKFIELD"; $('.link-field-1').submit(
document.forms[0].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; break;
case "cancel": case "cancel":
self.close(); $('.close-dialog').trigger('click');
break; break;
} }
} }
function loaded() function loaded()
{ {
window.name="LINKFIELD";
ckField(); ckField();
} }
$( document ).ready(function() {
loaded();
});
</script> </script>
</head> </head>
<body onload="loaded();" class="dialog"> <body 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 }}">
{% set branch = "<br/><b>" ~ fullBranch ~ "</b><br/>" %} <form class="link-field-1" action="linkfield2.php" method="post" target="LINKFIELD" style="padding: 5px 15px">
{% trans with {'%branch%' : branch} %}thesaurus:: Lier la branche de thesaurus au champ %branch%{% endtrans %} <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"> {% set branch = "<br/><b>" ~ fullBranch ~ "</b><br/>" %}
{% for fieldname, checked in fieldnames %} {% trans with {'%branch%' : branch} %}thesaurus:: Lier la branche de thesaurus au champ %branch%{% endtrans %}
<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 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> </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> </form>
</center>
</body> </body>
</html> </html>

View File

@@ -12,10 +12,23 @@
switch(button) switch(button)
{ {
case "submit": 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; break;
case "cancel": case "cancel":
self.close(); $('.close-dialog').trigger('click');
break; break;
} }
} }
@@ -23,11 +36,15 @@
{ {
window.name="LINKFIELD"; window.name="LINKFIELD";
} }
$( document ).ready(function() {
loaded();
});
</script> </script>
</head> </head>
<body onload="loaded();" class="dialog"> <body class="dialog">
<center> <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="piv" value="{{ piv }}">
<input type="hidden" name="bid" value="{{ bid }}"> <input type="hidden" name="bid" value="{{ bid }}">
<input type="hidden" name="tid" value="{{ tid }}"> <input type="hidden" name="tid" value="{{ tid }}">
@@ -68,10 +85,11 @@
{% else %} {% else %}
<div style='position:absolute; top:5px; left:0px; width:100%; text-align:center; color:green'>{{ 'thesaurus:: pas de reindexation' | trans }}</div> <div style='position:absolute; top:5px; left:0px; width:100%; text-align:center; color:green'>{{ 'thesaurus:: pas de reindexation' | trans }}</div>
{% endif %} {% endif %}
<br/> </div>
<input type="button" id="submit_button" value="{{ 'boutton::valider' | trans }}" onclick="clkBut('submit');"> <div class="thesaurus_confirm_bottom_block">
&nbsp;&nbsp;&nbsp; <input type="button" id="cancel_button" class="cancel_btn" value="{{ 'boutton::annuler' | trans }}" onclick="clkBut('cancel');">
<input type="button" id="cancel_button" 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> </form>
</center> </center>
</body> </body>

View File

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

View File

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

View File

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

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