Merge branch 'master' into PHRAS-3278-patch-for-4-1-3

This commit is contained in:
Nicolas Maillat
2021-01-15 15:19:11 +01:00
committed by GitHub
66 changed files with 3298 additions and 2612 deletions

51
.env
View File

@@ -1,4 +1,5 @@
PHRASEANET_PROJECT_NAME=Phraseanet PHRASEANET_PROJECT_NAME=Phraseanet
PHRASEANET_SERVER_NAME=localhost
# 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
@@ -37,6 +38,11 @@ SESSION_CACHE_LIMITER=off
# PHP LOG LEVEL : Possible Values: alert, error, warning, notice, debug # PHP LOG LEVEL : Possible Values: alert, error, warning, notice, debug
PHP_LOG_LEVEL=warning PHP_LOG_LEVEL=warning
# --------------- MYSQL CONFIGURATION --------------------
# Mysql max allowed packet
MYSQL_MAX_ALLOWED_PACKET=16M
# --------------- PHRASEANET CONFIGURATION -------------------- # --------------- PHRASEANET CONFIGURATION --------------------
# These variables are used in the configuration.yml . # These variables are used in the configuration.yml .
@@ -54,11 +60,6 @@ PHRASEANET_DB_PASSWORD=root
INSTALL_DB_TEMPLATE=DublinCore INSTALL_DB_TEMPLATE=DublinCore
INSTALL_APPBOX=ab_master INSTALL_APPBOX=ab_master
INSTALL_DATABOX=db_databox1 INSTALL_DATABOX=db_databox1
PHRASEANET_SERVER_NAME=localhost
# Mysql max allowed packet
MYSQL_MAX_ALLOWED_PACKET=16M
# binaries execution timeouts # binaries execution timeouts
PHRASEANET_FFMPEG_TIMEOUT=7200 PHRASEANET_FFMPEG_TIMEOUT=7200
PHRASEANET_FFPROBE_TIMEOUT=120 PHRASEANET_FFPROBE_TIMEOUT=120
@@ -79,15 +80,32 @@ PHRASEANET_API_AUTH_TOKEN_HEADER_ONLY=false
# Phraseanet mail configuration # Phraseanet mail configuration
PHRASEANET_EMITTER_EMAIL=phraseanet@example.com PHRASEANET_EMITTER_EMAIL=phraseanet@example.com
PHRASEANET_MAIL_OBJECT_PREFIX= PHRASEANET_MAIL_OBJECT_PREFIX="phraseanet"
PHRASEANET_SMTP_ENABLED=false PHRASEANET_SMTP_ENABLED=true
PHRASEANET_SMTP_HOST= PHRASEANET_SMTP_HOST=mailhog
PHRASEANET_SMTP_PORT= PHRASEANET_SMTP_PORT=1025
PHRASEANET_SMTP_AUTH_ENABLED=false PHRASEANET_SMTP_AUTH_ENABLED=false
PHRASEANET_SMTP_SECURE_MODE=tls PHRASEANET_SMTP_SECURE_MODE=null
PHRASEANET_SMTP_USER= PHRASEANET_SMTP_USER=
PHRASEANET_SMTP_PASSWORD= PHRASEANET_SMTP_PASSWORD=
# Phraseanet Workers setting
PHRASEANET_EXPLODE_WORKER=1
PHRASEANET_WORKER_assetsIngest=1
PHRASEANET_WORKER_createRecord=2
PHRASEANET_WORKER_deleteRecord=2
PHRASEANET_WORKER_exportMail=2
PHRASEANET_WORKER_exposeUpload=2
PHRASEANET_WORKER_ftp=1
PHRASEANET_WORKER_mainQueue=3
PHRASEANET_WORKER_populateIndex=1
PHRASEANET_WORKER_pullAssets=1
PHRASEANET_WORKER_recordEdit=2
PHRASEANET_WORKER_subdefCreation=1
PHRASEANET_WORKER_subtitle=1
PHRASEANET_WORKER_validationReminder=1
PHRASEANET_WORKER_webhook=1
PHRASEANET_WORKER_writeMetadatas=1
# Locale setting # Locale setting
@@ -100,6 +118,18 @@ LC_CTYPE=C.UTF-8
LC_TIME=C.UTF-8 LC_TIME=C.UTF-8
LC_NAME=C.UTF-8 LC_NAME=C.UTF-8
# --- EXTERNAL BINARIES SETTING ----
# ImageMagick default policy override
IMAGEMAGICK_POLICY_VERSION=6
IMAGEMAGICK_POLICY_WIDTH=16KP
IMAGEMAGICK_POLICY_HEIGHT=16KP
IMAGEMAGICK_POLICY_MAP=512MiB
IMAGEMAGICK_POLICY_MEMORY=256MiB
IMAGEMAGICK_POLICY_AREA=128MB
IMAGEMAGICK_POLICY_DISK=1GiB
IMAGEMAGICK_POLICY_TEMPORARY_PATH=/tmp
# --- DEV purpose --- # --- DEV purpose ---
# PhpMyAdmin port # PhpMyAdmin port
@@ -140,3 +170,4 @@ SSH_AUTH_SOCK=/dev/null
# Plugin support # Plugin support
PHRASEANET_PLUGINS= PHRASEANET_PLUGINS=
PHRASEANET_SSH_PRIVATE_KEY= PHRASEANET_SSH_PRIVATE_KEY=

View File

@@ -215,7 +215,7 @@ CMD ["php-fpm", "-F"]
FROM phraseanet-fpm as phraseanet-worker FROM phraseanet-fpm as phraseanet-worker
ENTRYPOINT ["docker/phraseanet/worker/entrypoint.sh"] ENTRYPOINT ["docker/phraseanet/worker/entrypoint.sh"]
CMD ["bin/console", "worker:execute"] CMD ["/bin/bash", "bin/run-worker.sh"]
######################################################################### #########################################################################
# phraseanet-nginx # phraseanet-nginx

View File

@@ -10230,15 +10230,15 @@ var workzone = function workzone(services) {
selection: new _selectable2.default(services, (0, _jquery2.default)('#baskets'), { selector: '.CHIM' }), selection: new _selectable2.default(services, (0, _jquery2.default)('#baskets'), { selector: '.CHIM' }),
refresh: refreshBaskets, refresh: refreshBaskets,
addElementToBasket: function addElementToBasket(options) { addElementToBasket: function addElementToBasket(options) {
var sbas_id = options.sbas_id, var dbId = options.dbId,
record_id = options.record_id, recordId = options.recordId,
event = options.event, event = options.event,
singleSelection = options.singleSelection; singleSelection = options.singleSelection;
singleSelection = !!singleSelection || false; singleSelection = !!singleSelection || false;
if ((0, _jquery2.default)('#baskets .SSTT.active').length === 1) { if ((0, _jquery2.default)('#baskets .SSTT.active').length === 1) {
return dropOnBask(event, (0, _jquery2.default)('#IMGT_' + sbas_id + '_' + record_id), (0, _jquery2.default)('#baskets .SSTT.active'), singleSelection); return dropOnBask(event, (0, _jquery2.default)('#IMGT_' + dbId + '_' + recordId), (0, _jquery2.default)('#baskets .SSTT.active'), singleSelection);
} else { } else {
humane.info(localeService.t('noActiveBasket')); humane.info(localeService.t('noActiveBasket'));
} }
@@ -10397,9 +10397,9 @@ var workzone = function workzone(services) {
}); });
function WorkZoneElementRemover(el, confirm) { function WorkZoneElementRemover(el, confirm) {
var context = el.data('context'); var context = (0, _jquery2.default)(el).data('context');
if (confirm !== true && (0, _jquery2.default)(el).hasClass('groupings') && warnOnRemove) { if (confirm !== true && ((0, _jquery2.default)(el).hasClass('groupings') || (0, _jquery2.default)(el).closest('.chim-wrapper').hasClass('chim-feedback-item')) && warnOnRemove) {
var buttons = {}; var buttons = {};
buttons[localeService.t('valider')] = function () { buttons[localeService.t('valider')] = function () {
@@ -10411,9 +10411,18 @@ var workzone = function workzone(services) {
(0, _jquery2.default)('#DIALOG-baskets').dialog('close').remove(); (0, _jquery2.default)('#DIALOG-baskets').dialog('close').remove();
}; };
var texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>'; var texte = '';
var title = '';
if ((0, _jquery2.default)(el).hasClass('groupings')) {
texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>';
title = localeService.t('removeTitle');
} else {
texte = '<p>' + localeService.t('confirmRemoveFeedBack') + '</p>';
title = localeService.t('removeRecordFeedbackTitle');
}
(0, _jquery2.default)('body').append('<div id="DIALOG-baskets"></div>'); (0, _jquery2.default)('body').append('<div id="DIALOG-baskets"></div>');
(0, _jquery2.default)('#DIALOG-baskets').attr('title', localeService.t('removeTitle')).empty().append(texte).dialog({ (0, _jquery2.default)('#DIALOG-baskets').attr('title', title).empty().append(texte).dialog({
autoOpen: false, autoOpen: false,
closeOnEscape: true, closeOnEscape: true,
resizable: false, resizable: false,
@@ -10519,7 +10528,9 @@ var workzone = function workzone(services) {
uiactive.addClass('ui-state-focus active'); uiactive.addClass('ui-state-focus active');
// reset selection when opening a basket type
workzoneOptions.selection.empty(); workzoneOptions.selection.empty();
appEvents.emit('broadcast.workzoneResultSelection', { asArray: [], serialized: "" });
getContent(uiactive); getContent(uiactive);
}, },
@@ -10884,6 +10895,10 @@ var workzone = function workzone(services) {
left: -20 left: -20
}, },
start: function start(event, ui) { start: function start(event, ui) {
if (!(0, _jquery2.default)(this).hasClass('selected')) {
return false;
}
var baskets = (0, _jquery2.default)('#baskets'); var baskets = (0, _jquery2.default)('#baskets');
baskets.append('<div class="top-scroller"></div>' + '<div class="bottom-scroller"></div>'); baskets.append('<div class="top-scroller"></div>' + '<div class="bottom-scroller"></div>');
(0, _jquery2.default)('.bottom-scroller', baskets).bind('mousemove', function () { (0, _jquery2.default)('.bottom-scroller', baskets).bind('mousemove', function () {
@@ -10898,9 +10913,9 @@ var workzone = function workzone(services) {
}, },
drag: function drag(event, ui) { drag: function drag(event, ui) {
if (appCommons.utilsModule.is_ctrl_key(event) || (0, _jquery2.default)(this).closest('.content').hasClass('grouping')) { if (appCommons.utilsModule.is_ctrl_key(event) || (0, _jquery2.default)(this).closest('.content').hasClass('grouping')) {
(0, _jquery2.default)('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length()); (0, _jquery2.default)('#dragDropCursor div').empty().append(workzoneOptions.selection.length() + ', ' + localeService.t('movedRecord'));
} else { } else {
(0, _jquery2.default)('#dragDropCursor div').empty().append(workzoneOptions.selection.length()); (0, _jquery2.default)('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length());
} }
} }
}); });
@@ -11042,7 +11057,7 @@ var workzone = function workzone(services) {
switch (action) { switch (action) {
case 'CHU2CHU': case 'CHU2CHU':
if (!appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV'; if (appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV';
break; break;
case 'IMGT2REG': case 'IMGT2REG':
case 'CHU2REG': case 'CHU2REG':
@@ -11125,6 +11140,8 @@ var workzone = function workzone(services) {
var publicationId = destKey.attr('data-publication-id'); var publicationId = destKey.attr('data-publication-id');
var exposeName = (0, _jquery2.default)('#expose_list').val(); var exposeName = (0, _jquery2.default)('#expose_list').val();
var assetsContainer = destKey.find('.expose_item_deployed'); var assetsContainer = destKey.find('.expose_item_deployed');
if (publicationId !== undefined) {
assetsContainer.empty().addClass('loading'); assetsContainer.empty().addClass('loading');
_jquery2.default.ajax({ _jquery2.default.ajax({
@@ -11146,6 +11163,7 @@ var workzone = function workzone(services) {
}); });
} }
} }
}
function fix() { function fix() {
_jquery2.default.ajax({ _jquery2.default.ajax({
@@ -63419,7 +63437,7 @@ var addToBasket = function addToBasket(services) {
var dbId = $el.data('db-id'); var dbId = $el.data('db-id');
var recordId = $el.data('record-id'); var recordId = $el.data('record-id');
appEvents.emit('workzone.doAddToBasket', { appEvents.emit('workzone.doAddToBasket', {
dbId: dbId, recordId: recordId, event: event.currentTarget dbId: dbId, recordId: recordId, event: event.currentTarget, singleSelection: true
}); });
}); });
}; };
@@ -65231,13 +65249,12 @@ var previewRecordService = function previewRecordService(services) {
event.preventDefault(); event.preventDefault();
closePreview(); closePreview();
}).on('dblclick', '.open-preview-action', function (event) { }).on('dblclick', '.open-preview-action', function (event) {
var $el = (0, _jquery2.default)(event.currentTarget); var $element = (0, _jquery2.default)(event.currentTarget);
// env, pos, contId, reload openPreview($element);
var reload = $el.data('reload') === true ? true : false;
_openPreview(event.currentTarget, $el.data('kind'), $el.data('position'), $el.data('id'), $el.data('kind'));
}).on('click', '.to-open-preview-action', function (event) { }).on('click', '.to-open-preview-action', function (event) {
event.preventDefault(); event.preventDefault();
(0, _jquery2.default)('.open-preview-action').trigger("dblclick"); var $element = (0, _jquery2.default)(event.currentTarget);
openPreview($element);
}); });
$previewContainer.on('click', '.preview-navigate-action', function (event) { $previewContainer.on('click', '.preview-navigate-action', function (event) {
event.preventDefault(); event.preventDefault();
@@ -65481,7 +65498,18 @@ var previewRecordService = function previewRecordService(services) {
(0, _jquery2.default)('#PREVIEWBOX img.record.zoomable').draggable(); (0, _jquery2.default)('#PREVIEWBOX img.record.zoomable').draggable();
} }
(0, _jquery2.default)('#SPANTITLE').empty().append(data.title); var basketIcon = '';
if (data.containerType !== null) {
if (data.containerType === 'feedback') {
basketIcon = "<img src='/assets/common/images/icons/basket_validation.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else if (data.containerType === 'push') {
basketIcon = "<img src='/assets/common/images/icons/basket_push.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else {
basketIcon = "<img src='/assets/common/images/icons/basket.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
}
}
(0, _jquery2.default)('#SPANTITLE').empty().append(basketIcon + data.title);
(0, _jquery2.default)('#PREVIEWTITLE_COLLLOGO').empty().append(data.collection_logo); (0, _jquery2.default)('#PREVIEWTITLE_COLLLOGO').empty().append(data.collection_logo);
(0, _jquery2.default)('#PREVIEWTITLE_COLLNAME').empty().append(data.databox_name + ' / ' + data.collection_name); (0, _jquery2.default)('#PREVIEWTITLE_COLLNAME').empty().append(data.databox_name + ' / ' + data.collection_name);
@@ -65544,6 +65572,12 @@ var previewRecordService = function previewRecordService(services) {
(0, _jquery2.default)("iframe", $sel).css('width', NW).css('height', NH); (0, _jquery2.default)("iframe", $sel).css('width', NW).css('height', NH);
} }
function openPreview($element) {
var reload = $element.data('reload') === true ? true : false;
// env, pos, contId, reload
_openPreview(event.currentTarget, $element.data('kind'), $element.data('position'), $element.data('id'), reload);
}
function closePreview() { function closePreview() {
options.open = false; options.open = false;
if (activeThumbnailFrame !== false) { if (activeThumbnailFrame !== false) {

View File

@@ -10230,15 +10230,15 @@ var workzone = function workzone(services) {
selection: new _selectable2.default(services, (0, _jquery2.default)('#baskets'), { selector: '.CHIM' }), selection: new _selectable2.default(services, (0, _jquery2.default)('#baskets'), { selector: '.CHIM' }),
refresh: refreshBaskets, refresh: refreshBaskets,
addElementToBasket: function addElementToBasket(options) { addElementToBasket: function addElementToBasket(options) {
var sbas_id = options.sbas_id, var dbId = options.dbId,
record_id = options.record_id, recordId = options.recordId,
event = options.event, event = options.event,
singleSelection = options.singleSelection; singleSelection = options.singleSelection;
singleSelection = !!singleSelection || false; singleSelection = !!singleSelection || false;
if ((0, _jquery2.default)('#baskets .SSTT.active').length === 1) { if ((0, _jquery2.default)('#baskets .SSTT.active').length === 1) {
return dropOnBask(event, (0, _jquery2.default)('#IMGT_' + sbas_id + '_' + record_id), (0, _jquery2.default)('#baskets .SSTT.active'), singleSelection); return dropOnBask(event, (0, _jquery2.default)('#IMGT_' + dbId + '_' + recordId), (0, _jquery2.default)('#baskets .SSTT.active'), singleSelection);
} else { } else {
humane.info(localeService.t('noActiveBasket')); humane.info(localeService.t('noActiveBasket'));
} }
@@ -10397,9 +10397,9 @@ var workzone = function workzone(services) {
}); });
function WorkZoneElementRemover(el, confirm) { function WorkZoneElementRemover(el, confirm) {
var context = el.data('context'); var context = (0, _jquery2.default)(el).data('context');
if (confirm !== true && (0, _jquery2.default)(el).hasClass('groupings') && warnOnRemove) { if (confirm !== true && ((0, _jquery2.default)(el).hasClass('groupings') || (0, _jquery2.default)(el).closest('.chim-wrapper').hasClass('chim-feedback-item')) && warnOnRemove) {
var buttons = {}; var buttons = {};
buttons[localeService.t('valider')] = function () { buttons[localeService.t('valider')] = function () {
@@ -10411,9 +10411,18 @@ var workzone = function workzone(services) {
(0, _jquery2.default)('#DIALOG-baskets').dialog('close').remove(); (0, _jquery2.default)('#DIALOG-baskets').dialog('close').remove();
}; };
var texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>'; var texte = '';
var title = '';
if ((0, _jquery2.default)(el).hasClass('groupings')) {
texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>';
title = localeService.t('removeTitle');
} else {
texte = '<p>' + localeService.t('confirmRemoveFeedBack') + '</p>';
title = localeService.t('removeRecordFeedbackTitle');
}
(0, _jquery2.default)('body').append('<div id="DIALOG-baskets"></div>'); (0, _jquery2.default)('body').append('<div id="DIALOG-baskets"></div>');
(0, _jquery2.default)('#DIALOG-baskets').attr('title', localeService.t('removeTitle')).empty().append(texte).dialog({ (0, _jquery2.default)('#DIALOG-baskets').attr('title', title).empty().append(texte).dialog({
autoOpen: false, autoOpen: false,
closeOnEscape: true, closeOnEscape: true,
resizable: false, resizable: false,
@@ -10519,7 +10528,9 @@ var workzone = function workzone(services) {
uiactive.addClass('ui-state-focus active'); uiactive.addClass('ui-state-focus active');
// reset selection when opening a basket type
workzoneOptions.selection.empty(); workzoneOptions.selection.empty();
appEvents.emit('broadcast.workzoneResultSelection', { asArray: [], serialized: "" });
getContent(uiactive); getContent(uiactive);
}, },
@@ -10884,6 +10895,10 @@ var workzone = function workzone(services) {
left: -20 left: -20
}, },
start: function start(event, ui) { start: function start(event, ui) {
if (!(0, _jquery2.default)(this).hasClass('selected')) {
return false;
}
var baskets = (0, _jquery2.default)('#baskets'); var baskets = (0, _jquery2.default)('#baskets');
baskets.append('<div class="top-scroller"></div>' + '<div class="bottom-scroller"></div>'); baskets.append('<div class="top-scroller"></div>' + '<div class="bottom-scroller"></div>');
(0, _jquery2.default)('.bottom-scroller', baskets).bind('mousemove', function () { (0, _jquery2.default)('.bottom-scroller', baskets).bind('mousemove', function () {
@@ -10898,9 +10913,9 @@ var workzone = function workzone(services) {
}, },
drag: function drag(event, ui) { drag: function drag(event, ui) {
if (appCommons.utilsModule.is_ctrl_key(event) || (0, _jquery2.default)(this).closest('.content').hasClass('grouping')) { if (appCommons.utilsModule.is_ctrl_key(event) || (0, _jquery2.default)(this).closest('.content').hasClass('grouping')) {
(0, _jquery2.default)('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length()); (0, _jquery2.default)('#dragDropCursor div').empty().append(workzoneOptions.selection.length() + ', ' + localeService.t('movedRecord'));
} else { } else {
(0, _jquery2.default)('#dragDropCursor div').empty().append(workzoneOptions.selection.length()); (0, _jquery2.default)('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length());
} }
} }
}); });
@@ -11042,7 +11057,7 @@ var workzone = function workzone(services) {
switch (action) { switch (action) {
case 'CHU2CHU': case 'CHU2CHU':
if (!appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV'; if (appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV';
break; break;
case 'IMGT2REG': case 'IMGT2REG':
case 'CHU2REG': case 'CHU2REG':
@@ -11125,6 +11140,8 @@ var workzone = function workzone(services) {
var publicationId = destKey.attr('data-publication-id'); var publicationId = destKey.attr('data-publication-id');
var exposeName = (0, _jquery2.default)('#expose_list').val(); var exposeName = (0, _jquery2.default)('#expose_list').val();
var assetsContainer = destKey.find('.expose_item_deployed'); var assetsContainer = destKey.find('.expose_item_deployed');
if (publicationId !== undefined) {
assetsContainer.empty().addClass('loading'); assetsContainer.empty().addClass('loading');
_jquery2.default.ajax({ _jquery2.default.ajax({
@@ -11146,6 +11163,7 @@ var workzone = function workzone(services) {
}); });
} }
} }
}
function fix() { function fix() {
_jquery2.default.ajax({ _jquery2.default.ajax({
@@ -63419,7 +63437,7 @@ var addToBasket = function addToBasket(services) {
var dbId = $el.data('db-id'); var dbId = $el.data('db-id');
var recordId = $el.data('record-id'); var recordId = $el.data('record-id');
appEvents.emit('workzone.doAddToBasket', { appEvents.emit('workzone.doAddToBasket', {
dbId: dbId, recordId: recordId, event: event.currentTarget dbId: dbId, recordId: recordId, event: event.currentTarget, singleSelection: true
}); });
}); });
}; };
@@ -65231,13 +65249,12 @@ var previewRecordService = function previewRecordService(services) {
event.preventDefault(); event.preventDefault();
closePreview(); closePreview();
}).on('dblclick', '.open-preview-action', function (event) { }).on('dblclick', '.open-preview-action', function (event) {
var $el = (0, _jquery2.default)(event.currentTarget); var $element = (0, _jquery2.default)(event.currentTarget);
// env, pos, contId, reload openPreview($element);
var reload = $el.data('reload') === true ? true : false;
_openPreview(event.currentTarget, $el.data('kind'), $el.data('position'), $el.data('id'), $el.data('kind'));
}).on('click', '.to-open-preview-action', function (event) { }).on('click', '.to-open-preview-action', function (event) {
event.preventDefault(); event.preventDefault();
(0, _jquery2.default)('.open-preview-action').trigger("dblclick"); var $element = (0, _jquery2.default)(event.currentTarget);
openPreview($element);
}); });
$previewContainer.on('click', '.preview-navigate-action', function (event) { $previewContainer.on('click', '.preview-navigate-action', function (event) {
event.preventDefault(); event.preventDefault();
@@ -65481,7 +65498,18 @@ var previewRecordService = function previewRecordService(services) {
(0, _jquery2.default)('#PREVIEWBOX img.record.zoomable').draggable(); (0, _jquery2.default)('#PREVIEWBOX img.record.zoomable').draggable();
} }
(0, _jquery2.default)('#SPANTITLE').empty().append(data.title); var basketIcon = '';
if (data.containerType !== null) {
if (data.containerType === 'feedback') {
basketIcon = "<img src='/assets/common/images/icons/basket_validation.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else if (data.containerType === 'push') {
basketIcon = "<img src='/assets/common/images/icons/basket_push.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else {
basketIcon = "<img src='/assets/common/images/icons/basket.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
}
}
(0, _jquery2.default)('#SPANTITLE').empty().append(basketIcon + data.title);
(0, _jquery2.default)('#PREVIEWTITLE_COLLLOGO').empty().append(data.collection_logo); (0, _jquery2.default)('#PREVIEWTITLE_COLLLOGO').empty().append(data.collection_logo);
(0, _jquery2.default)('#PREVIEWTITLE_COLLNAME').empty().append(data.databox_name + ' / ' + data.collection_name); (0, _jquery2.default)('#PREVIEWTITLE_COLLNAME').empty().append(data.databox_name + ' / ' + data.collection_name);
@@ -65544,6 +65572,12 @@ var previewRecordService = function previewRecordService(services) {
(0, _jquery2.default)("iframe", $sel).css('width', NW).css('height', NH); (0, _jquery2.default)("iframe", $sel).css('width', NW).css('height', NH);
} }
function openPreview($element) {
var reload = $element.data('reload') === true ? true : false;
// env, pos, contId, reload
_openPreview(event.currentTarget, $element.data('kind'), $element.data('position'), $element.data('id'), reload);
}
function closePreview() { function closePreview() {
options.open = false; options.open = false;
if (activeThumbnailFrame !== false) { if (activeThumbnailFrame !== false) {

View File

@@ -11,7 +11,7 @@ const addToBasket = (services) => {
let dbId = $el.data('db-id'); let dbId = $el.data('db-id');
let recordId = $el.data('record-id'); let recordId = $el.data('record-id');
appEvents.emit('workzone.doAddToBasket', { appEvents.emit('workzone.doAddToBasket', {
dbId, recordId, event: event.currentTarget dbId, recordId, event: event.currentTarget, singleSelection: true
}); });
}); });
}; };

View File

@@ -81,20 +81,14 @@ const previewRecordService = services => {
closePreview(); closePreview();
}) })
.on('dblclick', '.open-preview-action', event => { .on('dblclick', '.open-preview-action', event => {
let $el = $(event.currentTarget); let $element = $(event.currentTarget);
// env, pos, contId, reload openPreview($element);
let reload = $el.data('reload') === true ? true : false;
_openPreview(
event.currentTarget,
$el.data('kind'),
$el.data('position'),
$el.data('id'),
$el.data('kind')
);
}) })
.on('click', '.to-open-preview-action', event => { .on('click', '.to-open-preview-action', event => {
event.preventDefault(); event.preventDefault();
$( '.open-preview-action' ).trigger( "dblclick" ); let $element = $(event.currentTarget);
openPreview($element);
}) })
; ;
$previewContainer $previewContainer
@@ -387,7 +381,18 @@ const previewRecordService = services => {
$('#PREVIEWBOX img.record.zoomable').draggable(); $('#PREVIEWBOX img.record.zoomable').draggable();
} }
$('#SPANTITLE').empty().append(data.title); let basketIcon = '';
if (data.containerType !== null ) {
if (data.containerType === 'feedback') {
basketIcon = "<img src='/assets/common/images/icons/basket_validation.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else if (data.containerType === 'push') {
basketIcon = "<img src='/assets/common/images/icons/basket_push.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else {
basketIcon = "<img src='/assets/common/images/icons/basket.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
}
}
$('#SPANTITLE').empty().append(basketIcon + data.title);
$('#PREVIEWTITLE_COLLLOGO') $('#PREVIEWTITLE_COLLLOGO')
.empty() .empty()
.append(data.collection_logo); .append(data.collection_logo);
@@ -466,6 +471,18 @@ const previewRecordService = services => {
} }
function openPreview($element) {
let reload = $element.data('reload') === true ? true : false;
// env, pos, contId, reload
_openPreview(
event.currentTarget,
$element.data('kind'),
$element.data('position'),
$element.data('id'),
reload
);
}
function closePreview() { function closePreview() {
options.open = false; options.open = false;
if (activeThumbnailFrame !== false) { if (activeThumbnailFrame !== false) {

View File

@@ -214,11 +214,11 @@ const workzone = (services) => {
selection: new Selectable(services, $('#baskets'), {selector: '.CHIM'}), selection: new Selectable(services, $('#baskets'), {selector: '.CHIM'}),
refresh: refreshBaskets, refresh: refreshBaskets,
addElementToBasket: function (options) { addElementToBasket: function (options) {
let {sbas_id, record_id, event, singleSelection} = options; let {dbId, recordId, event, singleSelection} = options;
singleSelection = !!singleSelection || false; singleSelection = !!singleSelection || false;
if ($('#baskets .SSTT.active').length === 1) { if ($('#baskets .SSTT.active').length === 1) {
return dropOnBask(event, $('#IMGT_' + sbas_id + '_' + record_id), $('#baskets .SSTT.active'), singleSelection); return dropOnBask(event, $('#IMGT_' + dbId + '_' + recordId), $('#baskets .SSTT.active'), singleSelection);
} else { } else {
humane.info(localeService.t('noActiveBasket')); humane.info(localeService.t('noActiveBasket'));
} }
@@ -373,9 +373,9 @@ const workzone = (services) => {
}); });
function WorkZoneElementRemover(el, confirm) { function WorkZoneElementRemover(el, confirm) {
var context = el.data('context'); var context = $(el).data('context');
if (confirm !== true && $(el).hasClass('groupings') && warnOnRemove) { if (confirm !== true && ($(el).hasClass('groupings') || $(el).closest('.chim-wrapper').hasClass('chim-feedback-item')) && warnOnRemove) {
var buttons = {}; var buttons = {};
buttons[localeService.t('valider')] = function () { buttons[localeService.t('valider')] = function () {
@@ -387,9 +387,18 @@ const workzone = (services) => {
$('#DIALOG-baskets').dialog('close').remove(); $('#DIALOG-baskets').dialog('close').remove();
}; };
var texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>'; var texte = '';
var title = '';
if ($(el).hasClass('groupings')) {
texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>';
title = localeService.t('removeTitle');
} else {
texte = '<p>' + localeService.t('confirmRemoveFeedBack') + '</p>';
title = localeService.t('removeRecordFeedbackTitle');
}
$('body').append('<div id="DIALOG-baskets"></div>'); $('body').append('<div id="DIALOG-baskets"></div>');
$('#DIALOG-baskets').attr('title', localeService.t('removeTitle')) $('#DIALOG-baskets').attr('title', title)
.empty() .empty()
.append(texte) .append(texte)
.dialog({ .dialog({
@@ -500,7 +509,9 @@ const workzone = (services) => {
uiactive.addClass('ui-state-focus active'); uiactive.addClass('ui-state-focus active');
// reset selection when opening a basket type
workzoneOptions.selection.empty(); workzoneOptions.selection.empty();
appEvents.emit('broadcast.workzoneResultSelection', {asArray:[], serialized:""});
getContent(uiactive); getContent(uiactive);
@@ -874,6 +885,10 @@ const workzone = (services) => {
left: -20 left: -20
}, },
start: function (event, ui) { start: function (event, ui) {
if (!$(this).hasClass('selected')) {
return false;
}
var baskets = $('#baskets'); var baskets = $('#baskets');
baskets.append('<div class="top-scroller"></div>' + baskets.append('<div class="top-scroller"></div>' +
'<div class="bottom-scroller"></div>'); '<div class="bottom-scroller"></div>');
@@ -891,11 +906,10 @@ const workzone = (services) => {
}, },
drag: function (event, ui) { drag: function (event, ui) {
if (appCommons.utilsModule.is_ctrl_key(event) || $(this).closest('.content').hasClass('grouping')) { if (appCommons.utilsModule.is_ctrl_key(event) || $(this).closest('.content').hasClass('grouping')) {
$('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length()); $('#dragDropCursor div').empty().append(workzoneOptions.selection.length() + ', ' + localeService.t('movedRecord'));
} else { } else {
$('#dragDropCursor div').empty().append(workzoneOptions.selection.length()); $('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length());
} }
} }
}); });
window.workzoneOptions = workzoneOptions; window.workzoneOptions = workzoneOptions;
@@ -1043,7 +1057,7 @@ const workzone = (services) => {
switch (action) { switch (action) {
case 'CHU2CHU' : case 'CHU2CHU' :
if (!appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV'; if (appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV';
break; break;
case 'IMGT2REG': case 'IMGT2REG':
case 'CHU2REG' : case 'CHU2REG' :
@@ -1129,6 +1143,8 @@ const workzone = (services) => {
let publicationId = destKey.attr('data-publication-id'); let publicationId = destKey.attr('data-publication-id');
let exposeName = $('#expose_list').val(); let exposeName = $('#expose_list').val();
let assetsContainer = destKey.find('.expose_item_deployed'); let assetsContainer = destKey.find('.expose_item_deployed');
if (publicationId !== undefined) {
assetsContainer.empty().addClass('loading'); assetsContainer.empty().addClass('loading');
$.ajax({ $.ajax({
@@ -1151,6 +1167,7 @@ const workzone = (services) => {
}); });
} }
} }
}
function fix() { function fix() {
$.ajax({ $.ajax({

View File

@@ -331,6 +331,15 @@ workers:
user: guest user: guest
password: guest password: guest
vhost: / vhost: /
queues:
writeMetadatas: # this Q is "delayable" in case of record is locked
ttl_retry: 1500 # overwrite 1000 ms default delay
ttl_delayed: 10000 # overwrite 5000 ms default delay
subdefCreation: # this Q is "delayable" in case of record is locked
ttl_delayed: 10000 # overwrite 5000 ms default delay
pullAssets:
ttl_retry: 5000
max_retry : 5
externalservice: externalservice:
ginger: ginger:

View File

@@ -119,6 +119,7 @@ services:
- redis - redis
- rabbitmq - rabbitmq
- elasticsearch - elasticsearch
- phraseanet
environment: environment:
- PHRASEANET_PROJECT_NAME - PHRASEANET_PROJECT_NAME
- PHRASEANET_TRUSTED_PROXIES - PHRASEANET_TRUSTED_PROXIES
@@ -135,7 +136,30 @@ services:
- LC_CTYPE=C.UTF-8 - LC_CTYPE=C.UTF-8
- LC_TIME=C.UTF-8 - LC_TIME=C.UTF-8
- LC_NAME=C.UTF-8 - LC_NAME=C.UTF-8
- PHRASEANET_EXPLODE_WORKER
- PHRASEANET_WORKER_assetsIngest
- PHRASEANET_WORKER_createRecord
- PHRASEANET_WORKER_deleteRecord
- PHRASEANET_WORKER_exportMail
- PHRASEANET_WORKER_exposeUpload
- PHRASEANET_WORKER_ftp
- PHRASEANET_WORKER_mainQueue
- PHRASEANET_WORKER_populateIndex
- PHRASEANET_WORKER_pullAssets
- PHRASEANET_WORKER_recordEdit
- PHRASEANET_WORKER_subdefCreation
- PHRASEANET_WORKER_subtitle
- PHRASEANET_WORKER_validationReminder
- PHRASEANET_WORKER_webhook
- PHRASEANET_WORKER_writeMetadatas
- IMAGEMAGICK_POLICY_VERSION
- IMAGEMAGICK_POLICY_WIDTH
- IMAGEMAGICK_POLICY_HEIGHT
- IMAGEMAGICK_POLICY_MAP
- IMAGEMAGICK_POLICY_MEMORY
- IMAGEMAGICK_POLICY_AREA
- IMAGEMAGICK_POLICY_DISK
- IMAGEMAGICK_POLICY_TEMPORARY_PATH
volumes: volumes:
- ${PHRASEANET_CONFIG_DIR}:/var/alchemy/Phraseanet/config:rw - ${PHRASEANET_CONFIG_DIR}:/var/alchemy/Phraseanet/config:rw
- ${PHRASEANET_LOGS_DIR}:/var/alchemy/Phraseanet/logs:rw - ${PHRASEANET_LOGS_DIR}:/var/alchemy/Phraseanet/logs:rw

View File

@@ -38,7 +38,7 @@ if [ -f "$FILE" ]; then
if [[ $PHRASEANET_SMTP_ENABLED && $PHRASEANET_SMTP_ENABLED = true ]]; then 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-secure-mode $PHRASEANET_SMTP_SECURE_MODE
bin/setup system:config set registry.email.smtp-host $PHRASEANET_SMTP_HOST bin/setup system:config set registry.email.smtp-host $PHRASEANET_SMTP_HOST
bin/setup system:config set registry.email.smtp-port $PHRASEANET_SMTP_PORT bin/setup system:config set registry.email.smtp-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
@@ -49,7 +49,7 @@ if [ -f "$FILE" ]; then
if [[ -n ${PHRASEANET_ADMIN_ACCOUNT_ID} && $PHRASEANET_ADMIN_ACCOUNT_ID =~ ^[0-9]+$ ]]; then 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 bin/console user:password --user_id=$PHRASEANET_ADMIN_ACCOUNT_ID --password $PHRASEANET_ADMIN_ACCOUNT_PASSWORD -y
fi fi
echo `date +"%Y-%m-%d %H:%M:%S"` " - config/configuration.yml update by Phraseanet entrypoint.sh Finished !"
else else
echo "$FILE doesn't exist, entering setup..." echo "$FILE doesn't exist, entering setup..."
@@ -62,6 +62,7 @@ else
datas datas
runuser app -c docker/phraseanet/auto-install.sh runuser app -c docker/phraseanet/auto-install.sh
echo `date +"%Y-%m-%d %H:%M:%S"` " - End of Phraseanet Installation"
fi fi
if [ ${XDEBUG_ENABLED} == "1" ]; then if [ ${XDEBUG_ENABLED} == "1" ]; then
@@ -70,7 +71,8 @@ if [ ${XDEBUG_ENABLED} == "1" ]; then
fi fi
./docker/phraseanet/plugins/console init ./docker/phraseanet/plugins/console init
#rm -Rf cache/ rm -Rf cache/*
chmod 600 config/configuration.yml
chown -R app:app \ chown -R app:app \
cache \ cache \
@@ -84,6 +86,7 @@ if [ -d "plugins/" ];then
chown -R app:app plugins; chown -R app:app plugins;
fi fi
chown -R app:app datas & chown -R app:app datas && echo `date +"%Y-%m-%d %H:%M:%S"` " - Finished chown on datas by entreypoint" &
echo `date +"%Y-%m-%d %H:%M:%S"` " - Finished runnning Phraseanet entrypoint.sh"
bash -e docker-php-entrypoint $@ bash -e docker-php-entrypoint $@

View File

@@ -15,4 +15,47 @@ if [ ${XDEBUG_ENABLED} == "1" ]; then
docker-php-ext-enable xdebug docker-php-ext-enable xdebug
fi fi
if [ -f /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml ]; then
if [ ! -d $IMAGEMAGICK_POLICY_TEMPORARY_PATH ]; then
echo "$IMAGEMAGICK_POLICY_TEMPORARY_PATH does not exist lets create it"
mkdir -p $IMAGEMAGICK_POLICY_TEMPORARY_PATH
fi
sed -i "s/domain=\"resource\" name=\"memory\" value=\".*\"/domain=\"resource\" name=\"memory\" value=\"$IMAGEMAGICK_POLICY_MEMORY\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/domain=\"resource\" name=\"map\" value=\".*\"/domain=\"resource\" name=\"map\" value=\"$IMAGEMAGICK_POLICY_MAP\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/domain=\"resource\" name=\"width\" value=\".*\"/domain=\"resource\" name=\"width\" value=\"$IMAGEMAGICK_POLICY_WIDTH\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/domain=\"resource\" name=\"height\" value=\".*\"/domain=\"resource\" name=\"height\" value=\"$IMAGEMAGICK_POLICY_HEIGHT\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/domain=\"resource\" name=\"disk\" value=\".*\"/domain=\"resource\" name=\"disk\" value=\"$IMAGEMAGICK_POLICY_DISK\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/domain=\"resource\" name=\"area\" value=\".*\"/domain=\"resource\" name=\"area\" value=\"$IMAGEMAGICK_POLICY_AREA\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/.*domain=\"resource\" name=\"temporary-path\" value=\".*/<domain=\"resource\" name=\"temporary-path\" value=\"\\$IMAGEMAGICK_POLICY_TEMPORARY_PATH\" \/\>/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
fi
rm -rf bin/run-worker.sh
if [ ${PHRASEANET_EXPLODE_WORKER} == "1" ]; then
for i in `env | grep PHRASEANET_WORKER_ | cut -d'=' -f1`
do
queue_name="$(echo $i | cut -d'_' -f3)"
m=$i
command="bin/console worker:execute --queue-name=$queue_name -m ${!m} &"
echo $command >> bin/run-worker.sh
done
echo 'WORKER_NB_QUEUES=`env | grep PHRASEANET_WORKER_ | wc -l`
WORKER_LOOP_VALUE=20s
while true;
do
sleep $WORKER_LOOP_VALUE
nb_process=`ps faux | grep "worker:execute" | grep php | wc -l`
date_time_process=`date +"%Y-%m-%d %H:%M:%S"`
echo $date_time_process "-" $nb_process "running workers"
if [ $nb_process -lt $WORKER_NB_QUEUES ]
then
exit 1
break
fi
done ' >> bin/run-worker.sh
else
command="bin/console worker:execute"
echo $command >> bin/run-worker.sh
fi
runuser -u app -- $@ runuser -u app -- $@

View File

@@ -13,6 +13,7 @@ use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Controller\RecordsRequest; use Alchemy\Phrasea\Controller\RecordsRequest;
use Alchemy\Phrasea\Model\Entities\Basket; use Alchemy\Phrasea\Model\Entities\Basket;
use Alchemy\Phrasea\Model\Entities\BasketElement; use Alchemy\Phrasea\Model\Entities\BasketElement;
use Alchemy\Phrasea\Model\Entities\ValidationData;
use Alchemy\Phrasea\Model\Manipulator\BasketManipulator; use Alchemy\Phrasea\Model\Manipulator\BasketManipulator;
use Alchemy\Phrasea\Model\Repositories\BasketElementRepository; use Alchemy\Phrasea\Model\Repositories\BasketElementRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@@ -251,8 +252,25 @@ class BasketController extends Controller
continue; continue;
} }
$basket_element->getBasket()->removeElement($basket_element); $oldBasket = $basket_element->getBasket();
$oldBasket->removeElement($basket_element);
$basket->addElement($basket_element); $basket->addElement($basket_element);
// configure participant when moving from other type of basket to basket type feedback
if ($oldBasket->getValidation() == null && ($validationSession = $basket->getValidation()) !== null) {
$participants = $validationSession->getParticipants();
foreach ($participants as $participant) {
$validationData = new ValidationData();
$validationData->setParticipant($participant);
$validationData->setBasketElement($basket_element);
$this->getEntityManager()->persist($validationData);
}
}
$n++; $n++;
} }

View File

@@ -59,9 +59,12 @@ class LanguageController
'feed_require_fields' => $translator->trans('Vous n\'avez pas rempli tous les champ requis'), 'feed_require_fields' => $translator->trans('Vous n\'avez pas rempli tous les champ requis'),
'feed_require_feed' => $translator->trans('Vous n\'avez pas selectionne de fil de publication'), 'feed_require_feed' => $translator->trans('Vous n\'avez pas selectionne de fil de publication'),
'removeTitle' => $translator->trans('panier::Supression d\'un element d\'un reportage'), 'removeTitle' => $translator->trans('panier::Supression d\'un element d\'un reportage'),
'removeRecordFeedbackTitle' => $translator->trans('basket:feedback Delete item'),
'removeExposePublication' => $translator->trans('expose::Your are about to delete a publication from expose, please confirm your action !'), 'removeExposePublication' => $translator->trans('expose::Your are about to delete a publication from expose, please confirm your action !'),
'removeAssetPublication' => $translator->trans('expose::Your are about to delete an asset from a publication, please confirm your action !'), 'removeAssetPublication' => $translator->trans('expose::Your are about to delete an asset from a publication, please confirm your action !'),
'confirmRemoveReg' => $translator->trans('panier::Attention, vous etes sur le point de supprimer un element du reportage. Merci de confirmer votre action.'), 'confirmRemoveReg' => $translator->trans('panier::Attention, vous etes sur le point de supprimer un element du reportage. Merci de confirmer votre action.'),
'confirmRemoveFeedBack' => $translator->trans('basket:feedback Warning!You are about to delete one record from a feedback, please confirm your action'),
'movedRecord' => $translator->trans('basket:: Items are being to moved !'),
'advsearch_title' => $translator->trans('phraseanet::recherche avancee'), 'advsearch_title' => $translator->trans('phraseanet::recherche avancee'),
'bask_rename' => $translator->trans('panier:: renommer le panier'), 'bask_rename' => $translator->trans('panier:: renommer le panier'),
'reg_wrong_sbas' => $translator->trans('panier:: Un reportage ne peux recevoir que des elements provenants de la base ou il est enregistre'), 'reg_wrong_sbas' => $translator->trans('panier:: Un reportage ne peux recevoir que des elements provenants de la base ou il est enregistre'),

View File

@@ -146,7 +146,7 @@ class PushController extends Controller
$this->getDataboxLogger($element->getDatabox())->log( $this->getDataboxLogger($element->getDatabox())->log(
$element, $element,
Session_Logger::EVENT_VALIDATE, Session_Logger::EVENT_PUSH,
$user_receiver->getId(), $user_receiver->getId(),
'' ''
); );
@@ -386,7 +386,7 @@ class PushController extends Controller
$this->getDataboxLogger($basketElement->getRecord($this->app)->getDatabox())->log( $this->getDataboxLogger($basketElement->getRecord($this->app)->getDatabox())->log(
$basketElement->getRecord($this->app), $basketElement->getRecord($this->app),
Session_Logger::EVENT_PUSH, Session_Logger::EVENT_VALIDATE,
$participantUser->getId(), $participantUser->getId(),
'' ''
); );

View File

@@ -103,6 +103,18 @@ class RecordController extends Controller
$recordTitle = htmlspecialchars($record->get_title()); $recordTitle = htmlspecialchars($record->get_title());
} }
$containerType = null;
if ($env === 'BASK') {
if ($record->get_container()->getValidation()) {
$containerType = 'feedback';
} elseif ($record->get_container()->getPusher()) {
$containerType = 'push';
} else {
$containerType = 'basket';
}
}
return $this->app->json([ return $this->app->json([
"desc" => $this->render('prod/preview/caption.html.twig', [ "desc" => $this->render('prod/preview/caption.html.twig', [
'record' => $record, 'record' => $record,
@@ -131,6 +143,7 @@ class RecordController extends Controller
]), ]),
"pos" => $record->getNumber(), "pos" => $record->getNumber(),
"title" => $recordTitle, "title" => $recordTitle,
"containerType" => $containerType,
"databox_name" => $record->getDatabox()->get_dbname(), "databox_name" => $record->getDatabox()->get_dbname(),
"collection_name" => $record->getCollection()->get_name(), "collection_name" => $record->getCollection()->get_name(),
"collection_logo" => $record->getCollection()->getLogo($record->getBaseId(), $this->app), "collection_logo" => $record->getCollection()->getLogo($record->getBaseId(), $this->app),

View File

@@ -319,7 +319,8 @@ class LegacyRecordRepository implements RecordRepository
. $userFilter . $userFilter
. " WHERE g.rid_parent IN ( :storyIds )\n" . " WHERE g.rid_parent IN ( :storyIds )\n"
. " ORDER BY g.rid_parent, g.ord ASC\n" . " ORDER BY g.rid_parent, g.ord ASC\n"
. ") r\n" . ") r \n"
. "ORDER BY r.rid_parent, r.ord ASC\n"
; ;
} }

View File

@@ -6,6 +6,7 @@ use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection; use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection;
use Alchemy\Phrasea\WorkerManager\Queue\MessageHandler; use Alchemy\Phrasea\WorkerManager\Queue\MessageHandler;
use Alchemy\Phrasea\WorkerManager\Worker\WorkerInvoker; use Alchemy\Phrasea\WorkerManager\Worker\WorkerInvoker;
use Doctrine\DBAL\Connection;
use PhpAmqpLib\Channel\AMQPChannel; use PhpAmqpLib\Channel\AMQPChannel;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@@ -45,7 +46,7 @@ class WorkerExecuteCommand extends Command
if ($channel == null) { if ($channel == null) {
$output->writeln("Can't connect to rabbit, check configuration!"); $output->writeln("Can't connect to rabbit, check configuration!");
return; return 1;
} }
$serverConnection->declareExchange(); $serverConnection->declareExchange();
@@ -56,7 +57,7 @@ class WorkerExecuteCommand extends Command
if ($input->getOption('max-processes') != null && $maxProcesses == 0) { if ($input->getOption('max-processes') != null && $maxProcesses == 0) {
$output->writeln('<error>Invalid max-processes option.Need an integer</error>'); $output->writeln('<error>Invalid max-processes option.Need an integer</error>');
return; return 1;
} elseif($maxProcesses) { } elseif($maxProcesses) {
$workerInvoker->setMaxProcessPoolValue($maxProcesses); $workerInvoker->setMaxProcessPoolValue($maxProcesses);
} }
@@ -69,8 +70,26 @@ class WorkerExecuteCommand extends Command
$messageHandler = $this->container['alchemy_worker.message.handler']; $messageHandler = $this->container['alchemy_worker.message.handler'];
$messageHandler->consume($serverConnection, $workerInvoker, $argQueueName, $maxProcesses); $messageHandler->consume($serverConnection, $workerInvoker, $argQueueName, $maxProcesses);
/** @var Connection $dbConnection */
$dbConnection = $this->container['orm.em']->getConnection();
while (count($channel->callbacks)) { while (count($channel->callbacks)) {
$output->writeln("[*] Waiting for messages. To exit press CTRL+C"); $output->writeln("[*] Waiting for messages. To exit press CTRL+C");
// check connection for DB before given message to consumer
if($dbConnection->ping() === false){
$output->writeln("MySQL server is not available : retry to close and connect ....");
try {
$dbConnection->close();
$dbConnection->connect();
} catch (\Exception $e) {
// Mysql server can't be reconnected, so stop the worker
$serverConnection->connectionClose();
return 1;
}
}
$channel->wait(); $channel->wait();
} }

View File

@@ -13,19 +13,22 @@ use Alchemy\Phrasea\WorkerManager\Form\WorkerConfigurationType;
use Alchemy\Phrasea\WorkerManager\Form\WorkerFtpType; use Alchemy\Phrasea\WorkerManager\Form\WorkerFtpType;
use Alchemy\Phrasea\WorkerManager\Form\WorkerPullAssetsType; use Alchemy\Phrasea\WorkerManager\Form\WorkerPullAssetsType;
use Alchemy\Phrasea\WorkerManager\Form\WorkerSearchengineType; use Alchemy\Phrasea\WorkerManager\Form\WorkerSearchengineType;
use Alchemy\Phrasea\WorkerManager\Form\WorkerValidationReminderType;
use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection; use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher; use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
use Doctrine\ORM\OptimisticLockException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class AdminConfigurationController extends Controller class AdminConfigurationController extends Controller
{ {
public function indexAction(PhraseaApplication $app) public function indexAction(PhraseaApplication $app, Request $request)
{ {
/** @var AMQPConnection $serverConnection */
$serverConnection = $this->app['alchemy_worker.amqp.connection'];
/** @var WorkerRunningJobRepository $repoWorker */ /** @var WorkerRunningJobRepository $repoWorker */
$repoWorker = $app['repo.worker-running-job']; $repoWorker = $app['repo.worker-running-job'];
@@ -39,9 +42,10 @@ class AdminConfigurationController extends Controller
$workerRunningJob = $repoWorker->findByStatus($filterStatus); $workerRunningJob = $repoWorker->findByStatus($filterStatus);
return $this->render('admin/worker-manager/index.html.twig', [ return $this->render('admin/worker-manager/index.html.twig', [
'isConnected' => ($serverConnection->getChannel() != null) ? true : false, 'isConnected' => $this->getAMQPConnection()->getChannel() != null,
'workerRunningJob' => $workerRunningJob, 'workerRunningJob' => $workerRunningJob,
'reload' => false 'reload' => false,
'_fragment' => $request->get('_fragment') ?? 'worker-configuration',
]); ]);
} }
@@ -52,26 +56,54 @@ class AdminConfigurationController extends Controller
*/ */
public function configurationAction(PhraseaApplication $app, Request $request) public function configurationAction(PhraseaApplication $app, Request $request)
{ {
$retryQueueConfig = $this->getRetryQueueConfiguration(); $AMQPConnection = $this->getAMQPConnection();
$form = $app->form(new WorkerConfigurationType(), $retryQueueConfig); $conf = ['queues' => $this->getConf()->get(['workers', 'queues'], [])];
// ttl's are saved in conf in ms, display in form as sec.
foreach($conf['queues'] as $qname => $settings) {
foreach ($settings as $k=>$v) {
if(in_array($k, [AMQPConnection::TTL_RETRY, AMQPConnection::TTL_DELAYED])) {
$conf['queues'][$qname][$k] /= 1000.0;
}
}
}
$form = $app->form(new WorkerConfigurationType($AMQPConnection), $conf);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isValid()) { if ($form->isValid()) {
// save config in file // save config
$app['conf']->set(['workers', 'retry_queue'], $form->getData()); // too bad we must remove null entries from data to not save in conf
$_data = $form->getData();
$data = $conf['queues']; // we will save a patched conf (not only data) so custom settings will be preserved
foreach($_data['queues'] as $qname => $settings) {
$data[$qname] = [];
foreach ($settings as $k=>$v) {
if(!is_null($v)) { // ignore null values from form
if(in_array($k, [AMQPConnection::TTL_RETRY, AMQPConnection::TTL_DELAYED])) {
$v = (int)(1000 * (float)$v);
}
$data[$qname][$k] = $v;
}
}
}
ksort($data);
$app['conf']->set(['workers', 'queues'], $data);
/*
* todo : reinitialize q can't depend on form content :
* e.g. if a ttl_retry is blank in form, the value should go back to default, so the q should be reinit.
*
$queues = array_intersect_key(AMQPConnection::$defaultQueues, $retryQueueConfig); $queues = array_intersect_key(AMQPConnection::$defaultQueues, $retryQueueConfig);
$retryQueuesToReset = array_intersect_key(AMQPConnection::$defaultRetryQueues, array_flip($queues)); $retryQueuesToReset = array_intersect_key(AMQPConnection::$defaultRetryQueues, array_flip($queues));
/** @var AMQPConnection $serverConnection */
$serverConnection = $this->app['alchemy_worker.amqp.connection'];
// change the queue TTL // change the queue TTL
$serverConnection->reinitializeQueue($retryQueuesToReset); $AMQPConnection->reinitializeQueue($retryQueuesToReset);
$serverConnection->reinitializeQueue(AMQPConnection::$defaultDelayedQueues); $AMQPConnection->reinitializeQueue(AMQPConnection::$defaultDelayedQueues);
*/
return $app->redirectPath('worker_admin'); // too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-configuration']);
} }
return $this->render('admin/worker-manager/worker_configuration.html.twig', [ return $this->render('admin/worker-manager/worker_configuration.html.twig', [
@@ -84,7 +116,7 @@ class AdminConfigurationController extends Controller
/** @var WorkerRunningJobRepository $repoWorker */ /** @var WorkerRunningJobRepository $repoWorker */
$repoWorker = $app['repo.worker-running-job']; $repoWorker = $app['repo.worker-running-job'];
$reload = ($request->query->get('reload')) == 1 ? true : false ; $reload = ($request->query->get('reload') == 1);
$workerRunningJob = []; $workerRunningJob = [];
$filterStatus = []; $filterStatus = [];
@@ -114,8 +146,8 @@ class AdminConfigurationController extends Controller
/** /**
* @param Request $request * @param Request $request
* @param $workerId * @param $workerId
* @return \Symfony\Component\HttpFoundation\JsonResponse * @return JsonResponse
* @throws \Doctrine\ORM\OptimisticLockException * @throws OptimisticLockException
*/ */
public function changeStatusAction(Request $request, $workerId) public function changeStatusAction(Request $request, $workerId)
{ {
@@ -140,13 +172,11 @@ class AdminConfigurationController extends Controller
public function queueMonitorAction(PhraseaApplication $app, Request $request) public function queueMonitorAction(PhraseaApplication $app, Request $request)
{ {
$reload = ($request->query->get('reload')) == 1 ? true : false ; $reload = ($request->query->get('reload') == 1);
/** @var AMQPConnection $serverConnection */ $this->getAMQPConnection()->getChannel();
$serverConnection = $app['alchemy_worker.amqp.connection']; $this->getAMQPConnection()->declareExchange();
$serverConnection->getChannel(); $queuesStatus = $this->getAMQPConnection()->getQueuesStatus();
$serverConnection->declareExchange();
$queuesStatus = $serverConnection->getQueuesStatus();
return $this->render('admin/worker-manager/worker_queue_monitor.html.twig', [ return $this->render('admin/worker-manager/worker_queue_monitor.html.twig', [
'queuesStatus' => $queuesStatus, 'queuesStatus' => $queuesStatus,
@@ -162,10 +192,20 @@ class AdminConfigurationController extends Controller
return $this->app->json(['success' => false]); return $this->app->json(['success' => false]);
} }
/** @var AMQPConnection $serverConnection */ $this->getAMQPConnection()->reinitializeQueue([$queueName]);
$serverConnection = $this->app['alchemy_worker.amqp.connection'];
$serverConnection->reinitializeQueue([$queueName]); return $this->app->json(['success' => true]);
}
public function deleteQueueAction(PhraseaApplication $app, Request $request)
{
$queueName = $request->request->get('queueName');
if (empty($queueName)) {
return $this->app->json(['success' => false]);
}
$this->getAMQPConnection()->deleteQueue($queueName);
return $this->app->json(['success' => true]); return $this->app->json(['success' => true]);
} }
@@ -176,7 +216,8 @@ class AdminConfigurationController extends Controller
$repoWorker = $app['repo.worker-running-job']; $repoWorker = $app['repo.worker-running-job'];
$repoWorker->truncateWorkerTable(); $repoWorker->truncateWorkerTable();
return $app->redirectPath('worker_admin'); // too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-info']);
} }
public function deleteFinishedAction(PhraseaApplication $app) public function deleteFinishedAction(PhraseaApplication $app)
@@ -185,7 +226,8 @@ class AdminConfigurationController extends Controller
$repoWorker = $app['repo.worker-running-job']; $repoWorker = $app['repo.worker-running-job'];
$repoWorker->deleteFinishedWorks(); $repoWorker->deleteFinishedWorks();
return $app->redirectPath('worker_admin'); // too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-info']);
} }
public function searchengineAction(PhraseaApplication $app, Request $request) public function searchengineAction(PhraseaApplication $app, Request $request)
@@ -201,7 +243,8 @@ class AdminConfigurationController extends Controller
$this->getDispatcher()->dispatch(WorkerEvents::POPULATE_INDEX, new PopulateIndexEvent($populateInfo)); $this->getDispatcher()->dispatch(WorkerEvents::POPULATE_INDEX, new PopulateIndexEvent($populateInfo));
return $app->redirectPath('worker_admin'); // too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-searchengine']);
} }
return $this->render('admin/worker-manager/worker_searchengine.html.twig', [ return $this->render('admin/worker-manager/worker_searchengine.html.twig', [
@@ -211,14 +254,12 @@ class AdminConfigurationController extends Controller
public function subviewAction() public function subviewAction()
{ {
return $this->render('admin/worker-manager/worker_subview.html.twig', [ return $this->render('admin/worker-manager/worker_subview.html.twig', [ ]);
]);
} }
public function metadataAction() public function metadataAction()
{ {
return $this->render('admin/worker-manager/worker_metadata.html.twig', [ return $this->render('admin/worker-manager/worker_metadata.html.twig', [ ]);
]);
} }
public function ftpAction(PhraseaApplication $app, Request $request) public function ftpAction(PhraseaApplication $app, Request $request)
@@ -231,7 +272,8 @@ class AdminConfigurationController extends Controller
// save new ftp config // save new ftp config
$app['conf']->set(['workers', 'ftp'], array_merge($ftpConfig, $form->getData())); $app['conf']->set(['workers', 'ftp'], array_merge($ftpConfig, $form->getData()));
return $app->redirectPath('worker_admin'); // too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-ftp']);
} }
return $this->render('admin/worker-manager/worker_ftp.html.twig', [ return $this->render('admin/worker-manager/worker_ftp.html.twig', [
@@ -241,26 +283,60 @@ class AdminConfigurationController extends Controller
public function validationReminderAction(PhraseaApplication $app, Request $request) public function validationReminderAction(PhraseaApplication $app, Request $request)
{ {
$interval = $app['conf']->get(['workers', 'validationReminder', 'interval'], 7200); // nb : the "interval" for a loop-q is the ttl.
// so the setting is stored into the "queues" settings in conf.
// here only the "ttl_retry" can be set/changed in conf
$config = $this->getConf()->get(['workers', 'queues', MessagePublisher::VALIDATION_REMINDER_TYPE], []);
if(isset($config['ttl_retry'])) {
// all settings are in msec, but into the form we want large numbers in sec.
$config['ttl_retry'] /= 1000;
}
/** @var Form $form */
$form = $app->form(new WorkerValidationReminderType($this->getAMQPConnection()), $config);
if ($request->getMethod() == 'POST') { $form->handleRequest($request);
$reminderInterval = (int)$request->request->get('worker_reminder_interval'); if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
/** @var AMQPConnection $serverConnection */ switch($data['act']) {
$serverConnection = $this->app['alchemy_worker.amqp.connection']; case 'save' : // save the form content (settings)
$serverConnection->setQueue(MessagePublisher::VALIDATION_REMINDER_QUEUE); unset($data['act']); // don't save this
// the interval was displayed in sec. in form, convert back to msec
// save the period interval in second if(isset($data['ttl_retry'])) {
$app['conf']->set(['workers', 'validationReminder', 'interval'], $reminderInterval); $data['ttl_retry'] *= 1000;
}
$data = array_merge($config, $data);
$app['conf']->set(['workers', 'queues', MessagePublisher::VALIDATION_REMINDER_TYPE], $data);
$this->getAMQPConnection()->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_TYPE]);
break;
case 'start':
// reinitialize the validation reminder queues // reinitialize the validation reminder queues
$serverConnection->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_QUEUE]); $this->getAMQPConnection()->setQueue(MessagePublisher::VALIDATION_REMINDER_TYPE);
$this->app['alchemy_worker.message.publisher']->initializeLoopQueue(MessagePublisher::VALIDATION_REMINDER_TYPE); $this->getAMQPConnection()->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_TYPE]);
$this->getMessagePublisher()->initializeLoopQueue(MessagePublisher::VALIDATION_REMINDER_TYPE);
return $app->redirectPath('worker_admin'); break;
case 'stop':
$this->getAMQPConnection()->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_TYPE]);
break;
} }
// too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-reminder']);
}
// guess if the q is "running" = check if there are pending message on Q or loop-Q
$running = false;
$qStatuses = $this->getAMQPConnection()->getQueuesStatus();
foreach([
MessagePublisher::VALIDATION_REMINDER_TYPE,
$this->getAMQPConnection()->getLoopQueueName(MessagePublisher::VALIDATION_REMINDER_TYPE)
] as $qName) {
if(isset($qStatuses[$qName]) && $qStatuses[$qName]['messageCount'] > 0) {
$running = true;
}
}
return $this->render('admin/worker-manager/worker_validation_reminder.html.twig', [ return $this->render('admin/worker-manager/worker_validation_reminder.html.twig', [
'interval' => $interval 'form' => $form->createView(),
'running' => $running
]); ]);
} }
@@ -276,30 +352,74 @@ class AdminConfigurationController extends Controller
public function pullAssetsAction(PhraseaApplication $app, Request $request) public function pullAssetsAction(PhraseaApplication $app, Request $request)
{ {
$pullAssetsConfig = $this->getPullAssetsConfiguration(); $config = $this->getConf()->get(['workers', 'pull_assets'], []);
$form = $app->form(new WorkerPullAssetsType(), $pullAssetsConfig); // the "pullInterval" comes from the ttl_retry
$ttl_retry = $this->getConf()->get(['workers','queues', MessagePublisher::PULL_ASSETS_TYPE, 'ttl_retry'], null);
if(!is_null($ttl_retry)) {
$ttl_retry /= 1000; // form is in sec
}
$config['pullInterval'] = $ttl_retry;
$form = $app->form(new WorkerPullAssetsType(), $config);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
/** @var AMQPConnection $serverConnection */
$serverConnection = $this->app['alchemy_worker.amqp.connection'];
$serverConnection->setQueue(MessagePublisher::PULL_QUEUE);
// save new pull config $data = $form->getData();
$app['conf']->set(['workers', 'pull_assets'], array_merge($pullAssetsConfig, $form->getData())); switch($data['act']) {
case 'save' : // save the form content (settings) in 2 places
// reinitialize the pull queues $ttl_retry = $data['pullInterval'];
$serverConnection->reinitializeQueue([MessagePublisher::PULL_QUEUE]); unset($data['act'], $data['pullInterval'], $config['pullInterval']);
$this->app['alchemy_worker.message.publisher']->initializeLoopQueue(MessagePublisher::PULL_ASSETS_TYPE); // save most data under workers/pull_assets
$app['conf']->set(['workers', 'pull_assets'], array_merge($config, $data));
return $app->redirectPath('worker_admin'); // save ttl in the q settings
if(!is_null($ttl_retry)) {
$this->getConf()->set(['workers','queues', MessagePublisher::PULL_ASSETS_TYPE, 'ttl_retry'], 1000 * (int)$ttl_retry);
}
$this->getAMQPConnection()->reinitializeQueue([MessagePublisher::PULL_ASSETS_TYPE]);
break;
case 'start':
// reinitialize the validation reminder queues
$this->getAMQPConnection()->setQueue(MessagePublisher::PULL_ASSETS_TYPE);
$this->getAMQPConnection()->reinitializeQueue([MessagePublisher::PULL_ASSETS_TYPE]);
$this->getMessagePublisher()->initializeLoopQueue(MessagePublisher::PULL_ASSETS_TYPE);
break;
case 'stop':
$this->getAMQPConnection()->reinitializeQueue([MessagePublisher::PULL_ASSETS_TYPE]);
break;
} }
// too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-pull-assets']);
}
// guess if the q is "running" = check if there are pending message on Q or loop-Q
$running = false;
$qStatuses = $this->getAMQPConnection()->getQueuesStatus();
foreach([
MessagePublisher::PULL_ASSETS_TYPE,
$this->getAMQPConnection()->getLoopQueueName(MessagePublisher::PULL_ASSETS_TYPE)
] as $qName) {
if(isset($qStatuses[$qName]) && $qStatuses[$qName]['messageCount'] > 0) {
$running = true;
}
}
return $this->render('admin/worker-manager/worker_pull_assets.html.twig', [ return $this->render('admin/worker-manager/worker_pull_assets.html.twig', [
'form' => $form->createView() 'form' => $form->createView(),
'running' => $running
]); ]);
} }
/**
* @return MessagePublisher
*/
private function getMessagePublisher()
{
return $this->app['alchemy_worker.message.publisher'];
}
/** /**
* @return EventDispatcherInterface * @return EventDispatcherInterface
*/ */
@@ -333,18 +453,25 @@ class AdminConfigurationController extends Controller
return $data; return $data;
} }
private function getPullAssetsConfiguration()
{
return $this->app['conf']->get(['workers', 'pull_assets'], []);
}
private function getFtpConfiguration() private function getFtpConfiguration()
{ {
return $this->app['conf']->get(['workers', 'ftp'], []); return $this->getConf()->get(['workers', 'ftp'], []);
} }
private function getRetryQueueConfiguration() /**
* @return AMQPConnection
*/
private function getAMQPConnection()
{ {
return $this->app['conf']->get(['workers', 'retry_queue'], []); return $this->app['alchemy_worker.amqp.connection'];
} }
/**
* @return UrlGeneratorInterface
*/
private function getUrlGenerator()
{
return $this->app['url_generator'];
}
} }

View File

@@ -10,7 +10,7 @@ class AssetsCreationFailureEvent extends SfEvent
private $workerMessage; private $workerMessage;
private $count; private $count;
public function __construct($payload, $workerMessage, $count = 2) public function __construct($payload, $workerMessage, $count)
{ {
$this->payload = $payload; $this->payload = $payload;
$this->workerMessage = $workerMessage; $this->workerMessage = $workerMessage;

View File

@@ -12,7 +12,7 @@ class AssetsCreationRecordFailureEvent extends SfEvent
private $count; private $count;
private $workerJobId; private $workerJobId;
public function __construct($payload, $workerMessage = '', $count = 2, $workerJobId = 0) public function __construct($payload, $workerMessage, $count, $workerJobId )
{ {
$this->payload = $payload; $this->payload = $payload;
$this->workerMessage = $workerMessage; $this->workerMessage = $workerMessage;

View File

@@ -13,7 +13,7 @@ class ExportMailFailureEvent extends SfEvent
private $workerMessage; private $workerMessage;
private $count; private $count;
public function __construct($emitterUserId, $tokenValue, $destinationMails, $params, $workerMessage = '', $count = 2) public function __construct($emitterUserId, $tokenValue, $destinationMails, $params, $workerMessage, $count)
{ {
$this->emitterUserId = $emitterUserId; $this->emitterUserId = $emitterUserId;
$this->tokenValue = $tokenValue; $this->tokenValue = $tokenValue;

View File

@@ -14,7 +14,7 @@ class PopulateIndexFailureEvent extends SfEvent
private $count; private $count;
private $workerJobId; private $workerJobId;
public function __construct($host, $port, $indexName, $databoxId, $workerMessage = '', $count = 2, $workerJobId = 0) public function __construct($host, $port, $indexName, $databoxId, $workerMessage, $count, $workerJobId)
{ {
$this->host = $host; $this->host = $host;
$this->port = $port; $this->port = $port;

View File

@@ -12,7 +12,7 @@ class SubdefinitionCreationFailureEvent extends RecordEvent
private $count; private $count;
private $workerJobId; private $workerJobId;
public function __construct(RecordInterface $record, $subdefName, $workerMessage = '', $count = 2, $workerJobId = 0) public function __construct(RecordInterface $record, $subdefName, $workerMessage, $count, $workerJobId)
{ {
parent::__construct($record); parent::__construct($record);

View File

@@ -11,7 +11,7 @@ class WebhookDeliverFailureEvent extends SfEvent
private $count; private $count;
private $deleveryId; private $deleveryId;
public function __construct($webhookEventId, $workerMessage, $count = 2, $deleveryId = null) public function __construct($webhookEventId, $workerMessage, $count, $deleveryId = null)
{ {
$this->webhookEventId = $webhookEventId; $this->webhookEventId = $webhookEventId;
$this->workerMessage = $workerMessage; $this->workerMessage = $workerMessage;

View File

@@ -0,0 +1,68 @@
<?php
namespace Alchemy\Phrasea\WorkerManager\Form;
use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
class QueueSettingsType extends AbstractType
{
private $AMQPConnection;
private $baseQueueName;
public function __construct(AMQPConnection $AMQPConnection, string $baseQueueName)
{
$this->AMQPConnection = $AMQPConnection;
$this->baseQueueName = $baseQueueName;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->add('n_workers', HiddenType::class, [
'label' => 'admin::workermanager:tab:workerconfig:n_workers',
'required' => false,
'attr' => [
'placeholder' => 1
]
]);
if($this->AMQPConnection->hasRetryQueue($this->baseQueueName) || $this->AMQPConnection->hasLoopQueue($this->baseQueueName)) {
$builder
->add('max_retry', IntegerType::class, [
'label' => 'admin::workermanager:tab:workerconfig:max retry',
'required' => false,
'attr' => [
'placeholder' => $this->AMQPConnection->getDefaultSetting($this->baseQueueName, AMQPConnection::MAX_RETRY),
//'class'=>'col'
]
])
->add('ttl_retry', IntegerType::class, [
'label' => 'admin::workermanager:tab:workerconfig:retry delay in seconds',
'required' => false,
'attr' => [
'placeholder' => $this->AMQPConnection->getDefaultSetting($this->baseQueueName, AMQPConnection::TTL_RETRY) / 1000.0,
//'class'=>'col'
]
]);
}
if($this->AMQPConnection->hasDelayedQueue($this->baseQueueName)) {
$builder->add('ttl_delayed', IntegerType::class, [
'label' => 'admin::workermanager:tab:workerconfig:delayed delay in seconds',
'required' => false,
'attr' => [
'placeholder' => $this->AMQPConnection->getDefaultSetting($this->baseQueueName, AMQPConnection::TTL_DELAYED) / 1000.0,
//'class'=>'col'
]
]);
}
}
public function getName()
{
return 'queue_settings';
}
}

View File

@@ -2,48 +2,43 @@
namespace Alchemy\Phrasea\WorkerManager\Form; namespace Alchemy\Phrasea\WorkerManager\Form;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher; use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class WorkerConfigurationType extends AbstractType class WorkerConfigurationType extends AbstractType
{ {
private $AMQPConnection;
public function __construct(AMQPConnection $AMQPConnection)
{
$this->AMQPConnection = $AMQPConnection;
}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
parent::buildForm($builder, $options); parent::buildForm($builder, $options);
$builder $g = $builder->create("queues", FormType::class, ['attr'=>['class'=>'form-row']]);
->add(MessagePublisher::ASSETS_INGEST_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Ingest retry delay in ms' foreach($this->AMQPConnection->getBaseQueueNames() as $baseQueueName) {
]) if($this->AMQPConnection->hasRetryQueue($baseQueueName)
->add(MessagePublisher::CREATE_RECORD_TYPE, 'text', [ || $this->AMQPConnection->hasLoopQueue($baseQueueName)
'label' => 'admin::workermanager:tab:workerconfig: Create record retry delay in ms' || $this->AMQPConnection->hasDelayedQueue($baseQueueName)
]) ) {
->add(MessagePublisher::SUBDEF_CREATION_TYPE, 'text', [ $f = new QueueSettingsType($this->AMQPConnection, $baseQueueName);
'label' => 'admin::workermanager:tab:workerconfig: Subdefinition retry delay in ms' $g->add($baseQueueName, $f, ['attr' => ['class' => 'norow'], 'block_name' => 'queue']);
]) }
->add(MessagePublisher::WRITE_METADATAS_TYPE, 'text', [ }
'label' => 'admin::workermanager:tab:workerconfig: Metadatas retry delay in ms'
]) $builder->add($g);
->add(MessagePublisher::WEBHOOK_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Webhook retry delay in ms' $builder->add("boutton::appliquer", SubmitType::class,
]) [
->add(MessagePublisher::EXPORT_MAIL_TYPE, 'text', [ 'label' => "boutton::appliquer"
'label' => 'admin::workermanager:tab:workerconfig: Export mail retry delay in ms' ]);
])
->add(MessagePublisher::POPULATE_INDEX_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Populate Index retry delay in ms'
])
->add(MessagePublisher::FTP_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Ftp retry delay in ms (default 3 min)'
])
->add('delayedSubdef', 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Subdef delay in ms'
])
->add('delayedWriteMeta', 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Write meta delay in ms'
])
;
} }
public function getName() public function getName()

View File

@@ -3,6 +3,7 @@
namespace Alchemy\Phrasea\WorkerManager\Form; namespace Alchemy\Phrasea\WorkerManager\Form;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class WorkerFtpType extends AbstractType class WorkerFtpType extends AbstractType
@@ -12,19 +13,19 @@ class WorkerFtpType extends AbstractType
parent::buildForm($builder, $options); parent::buildForm($builder, $options);
$builder $builder
->add('proxy', 'text', [ ->add('proxy', TextType::class, [
'label' => 'admin::workermanager:tab:ftp: Proxy', 'label' => 'admin::workermanager:tab:ftp: Proxy',
'required' => false 'required' => false
]) ])
->add('proxyPort', 'text', [ ->add('proxyPort', TextType::class, [
'label' => 'admin::workermanager:tab:ftp: Proxy port', 'label' => 'admin::workermanager:tab:ftp: Proxy port',
'required' => false 'required' => false
]) ])
->add('proxyUser', 'text', [ ->add('proxyUser', TextType::class, [
'label' => 'admin::workermanager:tab:ftp: Proxy user', 'label' => 'admin::workermanager:tab:ftp: Proxy user',
'required' => false 'required' => false
]) ])
->add('proxyPassword', 'text', [ ->add('proxyPassword', TextType::class, [
'label' => 'admin::workermanager:tab:ftp: Proxy password', 'label' => 'admin::workermanager:tab:ftp: Proxy password',
'required' => false 'required' => false
]) ])

View File

@@ -3,6 +3,9 @@
namespace Alchemy\Phrasea\WorkerManager\Form; namespace Alchemy\Phrasea\WorkerManager\Form;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class WorkerPullAssetsType extends AbstractType class WorkerPullAssetsType extends AbstractType
@@ -11,20 +14,36 @@ class WorkerPullAssetsType extends AbstractType
{ {
parent::buildForm($builder, $options); parent::buildForm($builder, $options);
// because this form will have 3 submit buttons - to use the same route -, this "act" field
// will reflect the value of the clicked button (js)
// !!! tried: using symfony "getClickedButton()" does to NOT work (submit button values seems not sent in request ?)
$builder $builder
->add('UploaderApiBaseUri', 'text', [ ->add('act', HiddenType::class, [
'attr' => [
'class' => 'act'
]
]);
$builder
->add('UploaderApiBaseUri', TextType::class, [
'label' => 'admin::workermanager:tab:pullassets: Uploader api base uri' 'label' => 'admin::workermanager:tab:pullassets: Uploader api base uri'
]) ])
->add('clientSecret', 'text', [ ->add('clientSecret', TextType::class, [
'label' => 'admin::workermanager:tab:pullassets: Client secret' 'label' => 'admin::workermanager:tab:pullassets: Client secret'
]) ])
->add('clientId', 'text', [ ->add('clientId', TextType::class, [
'label' => 'admin::workermanager:tab:pullassets: Client ID' 'label' => 'admin::workermanager:tab:pullassets: Client ID'
]) ])
->add('pullInterval', 'text', [ ->add('pullInterval', TextType::class, [
'label' => 'admin::workermanager:tab:pullassets: Fetching interval in second' 'label' => 'admin::workermanager:tab:pullassets: Fetching interval in second'
]) ]);
;
$builder
->add("boutton::appliquer", SubmitType::class, [
'label' => "boutton::appliquer",
'attr' => ['value' => 'save']
]);
} }
public function getName() public function getName()

View File

@@ -3,6 +3,8 @@
namespace Alchemy\Phrasea\WorkerManager\Form; namespace Alchemy\Phrasea\WorkerManager\Form;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
@@ -15,18 +17,18 @@ class WorkerSearchengineType extends AbstractType
parent::buildForm($builder, $options); parent::buildForm($builder, $options);
$builder $builder
->add('host', 'text', [ ->add('host', TextType::class, [
'label' => 'admin::workermanager:tab:searchengine: Elasticsearch server host', 'label' => 'admin::workermanager:tab:searchengine: Elasticsearch server host',
'constraints' => new NotBlank(), 'constraints' => new NotBlank(),
]) ])
->add('port', 'integer', [ ->add('port', IntegerType::class, [
'label' => 'admin::workermanager:tab:searchengine: Elasticsearch service port', 'label' => 'admin::workermanager:tab:searchengine: Elasticsearch service port',
'constraints' => [ 'constraints' => [
new Range(['min' => 1, 'max' => 65535]), new Range(['min' => 1, 'max' => 65535]),
new NotBlank() new NotBlank()
] ]
]) ])
->add('indexName', 'text', [ ->add('indexName', TextType::class, [
'label' => 'admin::workermanager:tab:searchengine: Elasticsearch index name', 'label' => 'admin::workermanager:tab:searchengine: Elasticsearch index name',
'constraints' => new NotBlank(), 'constraints' => new NotBlank(),
'attr' =>['data-class'=>'inline'] 'attr' =>['data-class'=>'inline']

View File

@@ -0,0 +1,68 @@
<?php
namespace Alchemy\Phrasea\WorkerManager\Form;
use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class WorkerValidationReminderType extends AbstractType
{
private $AMQPConnection;
public function __construct(AMQPConnection $AMQPConnection)
{
$this->AMQPConnection = $AMQPConnection;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
// because this form will have 3 submit buttons - to use the same route -, this "act" field
// will reflect the value of the clicked button (js)
// !!! tried: using symfony "getClickedButton()" does to NOT work (submit button values seems not sent in request ?)
$builder
->add('act', HiddenType::class, [
'attr' => [
'class' => 'act'
]
]);
// every ttl is in msec, we display this large one (loop q) in sec in form.
$defaultInterval = $this->AMQPConnection->getDefaultSetting(MessagePublisher::VALIDATION_REMINDER_TYPE, AMQPConnection::TTL_RETRY) / 1000;
$builder
->add('ttl_retry', TextType::class, [
'label' => 'admin::workermanager:tab:Reminder: Interval in second',
'attr' => [
'placeholder' => $defaultInterval
]
]);
$builder
->add("boutton::appliquer", SubmitType::class, [
'label' => "boutton::appliquer",
'attr' => ['value' => 'save']
]);
/*
$builder
->add("submit", ButtonType::class, [
'label' => "start",
// 'data' => 'truc',
// 'empty_data' => 'machin',
'attr'=>[
'value' => 'start',
'name' => 'zobi'
]
]);
*/
}
// public function getName()
// {
// return 'worker_pullAssets';
// }
}

View File

@@ -45,61 +45,81 @@ class ControllerServiceProvider implements ControllerProviderInterface, ServiceP
$firewall->requireRight(\ACL::TASKMANAGER); $firewall->requireRight(\ACL::TASKMANAGER);
}); });
/** @uses AdminConfigurationController::indexAction */
$controllers->match('/', 'controller.worker.admin.configuration:indexAction') $controllers->match('/', 'controller.worker.admin.configuration:indexAction')
->method('GET') ->method('GET')
->bind('worker_admin'); ->bind('worker_admin');
/** @uses AdminConfigurationController::configurationAction */
$controllers->match('/configuration', 'controller.worker.admin.configuration:configurationAction') $controllers->match('/configuration', 'controller.worker.admin.configuration:configurationAction')
->method('GET|POST') ->method('GET|POST')
->bind('worker_admin_configuration'); ->bind('worker_admin_configuration');
/** @uses AdminConfigurationController::infoAction */
$controllers->match('/info', 'controller.worker.admin.configuration:infoAction') $controllers->match('/info', 'controller.worker.admin.configuration:infoAction')
->method('GET') ->method('GET')
->bind('worker_admin_info'); ->bind('worker_admin_info');
/** @uses AdminConfigurationController::truncateTableAction */
$controllers->match('/truncate', 'controller.worker.admin.configuration:truncateTableAction') $controllers->match('/truncate', 'controller.worker.admin.configuration:truncateTableAction')
->method('POST') ->method('POST')
->bind('worker_admin_truncate'); ->bind('worker_admin_truncate');
/** @uses AdminConfigurationController::deleteFinishedAction */
$controllers->match('/delete-finished', 'controller.worker.admin.configuration:deleteFinishedAction') $controllers->match('/delete-finished', 'controller.worker.admin.configuration:deleteFinishedAction')
->method('POST') ->method('POST')
->bind('worker_admin_delete_finished'); ->bind('worker_admin_delete_finished');
/** @uses AdminConfigurationController::searchengineAction */
$controllers->match('/searchengine', 'controller.worker.admin.configuration:searchengineAction') $controllers->match('/searchengine', 'controller.worker.admin.configuration:searchengineAction')
->method('GET|POST') ->method('GET|POST')
->bind('worker_admin_searchengine'); ->bind('worker_admin_searchengine');
/** @uses AdminConfigurationController::subviewAction */
$controllers->match('/subview', 'controller.worker.admin.configuration:subviewAction') $controllers->match('/subview', 'controller.worker.admin.configuration:subviewAction')
->method('GET|POST') ->method('GET|POST')
->bind('worker_admin_subview'); ->bind('worker_admin_subview');
/** @uses AdminConfigurationController::metadataAction */
$controllers->match('/metadata', 'controller.worker.admin.configuration:metadataAction') $controllers->match('/metadata', 'controller.worker.admin.configuration:metadataAction')
->method('GET|POST') ->method('GET|POST')
->bind('worker_admin_metadata'); ->bind('worker_admin_metadata');
/** @uses AdminConfigurationController::ftpAction */
$controllers->match('/ftp', 'controller.worker.admin.configuration:ftpAction') $controllers->match('/ftp', 'controller.worker.admin.configuration:ftpAction')
->method('GET|POST') ->method('GET|POST')
->bind('worker_admin_ftp'); ->bind('worker_admin_ftp');
/** @uses AdminConfigurationController::populateStatusAction */
$controllers->get('/populate-status', 'controller.worker.admin.configuration:populateStatusAction') $controllers->get('/populate-status', 'controller.worker.admin.configuration:populateStatusAction')
->bind('worker_admin_populate_status'); ->bind('worker_admin_populate_status');
/** @uses AdminConfigurationController::pullAssetsAction */
$controllers->match('/pull-assets', 'controller.worker.admin.configuration:pullAssetsAction') $controllers->match('/pull-assets', 'controller.worker.admin.configuration:pullAssetsAction')
->method('GET|POST') ->method('GET|POST')
->bind('worker_admin_pullAssets'); ->bind('worker_admin_pullAssets');
/** @uses AdminConfigurationController::validationReminderAction */
$controllers->match('/validation-reminder', 'controller.worker.admin.configuration:validationReminderAction') $controllers->match('/validation-reminder', 'controller.worker.admin.configuration:validationReminderAction')
->method('GET|POST') ->method('GET|POST')
->bind('worker_admin_validationReminder'); ->bind('worker_admin_validationReminder');
/** @uses AdminConfigurationController::queueMonitorAction */
$controllers->match('/queue-monitor', 'controller.worker.admin.configuration:queueMonitorAction') $controllers->match('/queue-monitor', 'controller.worker.admin.configuration:queueMonitorAction')
->method('GET') ->method('GET')
->bind('worker_admin_queue_monitor'); ->bind('worker_admin_queue_monitor');
/** @uses AdminConfigurationController::purgeQueueAction */
$controllers->match('/purge-queue', 'controller.worker.admin.configuration:purgeQueueAction') $controllers->match('/purge-queue', 'controller.worker.admin.configuration:purgeQueueAction')
->method('POST') ->method('POST')
->bind('worker_admin_purge_queue'); ->bind('worker_admin_purge_queue');
/** @uses AdminConfigurationController::deleteQueueAction */
$controllers->match('/delete-queue', 'controller.worker.admin.configuration:deleteQueueAction')
->method('POST')
->bind('worker_admin_delete_queue');
/** @uses AdminConfigurationController::changeStatusAction */
$controllers->match('/{workerId}/change-status', 'controller.worker.admin.configuration:changeStatusAction') $controllers->match('/{workerId}/change-status', 'controller.worker.admin.configuration:changeStatusAction')
->method('POST') ->method('POST')
->assert('workerId', '\d+') ->assert('workerId', '\d+')

View File

@@ -3,6 +3,7 @@
namespace Alchemy\Phrasea\WorkerManager\Queue; namespace Alchemy\Phrasea\WorkerManager\Queue;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess; use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
use Exception;
use PhpAmqpLib\Channel\AMQPChannel; use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Wire\AMQPTable; use PhpAmqpLib\Wire\AMQPTable;
@@ -20,67 +21,109 @@ class AMQPConnection
private $hostConfig; private $hostConfig;
private $conf; private $conf;
public static $defaultQueues = [
MessagePublisher::WRITE_METADATAS_TYPE => MessagePublisher::METADATAS_QUEUE,
MessagePublisher::SUBDEF_CREATION_TYPE => MessagePublisher::SUBDEF_QUEUE,
MessagePublisher::EXPORT_MAIL_TYPE => MessagePublisher::EXPORT_QUEUE,
MessagePublisher::WEBHOOK_TYPE => MessagePublisher::WEBHOOK_QUEUE,
MessagePublisher::ASSETS_INGEST_TYPE => MessagePublisher::ASSETS_INGEST_QUEUE,
MessagePublisher::CREATE_RECORD_TYPE => MessagePublisher::CREATE_RECORD_QUEUE,
MessagePublisher::PULL_ASSETS_TYPE => MessagePublisher::PULL_QUEUE,
MessagePublisher::POPULATE_INDEX_TYPE => MessagePublisher::POPULATE_INDEX_QUEUE,
MessagePublisher::DELETE_RECORD_TYPE => MessagePublisher::DELETE_RECORD_QUEUE,
MessagePublisher::MAIN_QUEUE_TYPE => MessagePublisher::MAIN_QUEUE,
MessagePublisher::SUBTITLE_TYPE => MessagePublisher::SUBTITLE_QUEUE,
MessagePublisher::FTP_TYPE => MessagePublisher::FTP_QUEUE,
MessagePublisher::VALIDATION_REMINDER_TYPE => MessagePublisher::VALIDATION_REMINDER_QUEUE,
MessagePublisher::EXPOSE_UPLOAD_TYPE => MessagePublisher::EXPOSE_UPLOAD_QUEUE,
MessagePublisher::RECORD_EDIT_TYPE => MessagePublisher::RECORD_EDIT_QUEUE
];
// the corresponding worker queues and retry queues, loop queue
public static $defaultRetryQueues = [
MessagePublisher::METADATAS_QUEUE => MessagePublisher::RETRY_METADATAS_QUEUE,
MessagePublisher::SUBDEF_QUEUE => MessagePublisher::RETRY_SUBDEF_QUEUE,
MessagePublisher::EXPORT_QUEUE => MessagePublisher::RETRY_EXPORT_QUEUE,
MessagePublisher::WEBHOOK_QUEUE => MessagePublisher::RETRY_WEBHOOK_QUEUE,
MessagePublisher::ASSETS_INGEST_QUEUE => MessagePublisher::RETRY_ASSETS_INGEST_QUEUE,
MessagePublisher::CREATE_RECORD_QUEUE => MessagePublisher::RETRY_CREATE_RECORD_QUEUE,
MessagePublisher::POPULATE_INDEX_QUEUE => MessagePublisher::RETRY_POPULATE_INDEX_QUEUE,
MessagePublisher::PULL_QUEUE => MessagePublisher::LOOP_PULL_QUEUE,
MessagePublisher::FTP_QUEUE => MessagePublisher::RETRY_FTP_QUEUE,
MessagePublisher::VALIDATION_REMINDER_QUEUE => MessagePublisher::LOOP_VALIDATION_REMINDER_QUEUE
];
public static $defaultFailedQueues = [
MessagePublisher::WRITE_METADATAS_TYPE => MessagePublisher::FAILED_METADATAS_QUEUE,
MessagePublisher::SUBDEF_CREATION_TYPE => MessagePublisher::FAILED_SUBDEF_QUEUE,
MessagePublisher::EXPORT_MAIL_TYPE => MessagePublisher::FAILED_EXPORT_QUEUE,
MessagePublisher::WEBHOOK_TYPE => MessagePublisher::FAILED_WEBHOOK_QUEUE,
MessagePublisher::ASSETS_INGEST_TYPE => MessagePublisher::FAILED_ASSETS_INGEST_QUEUE,
MessagePublisher::CREATE_RECORD_TYPE => MessagePublisher::FAILED_CREATE_RECORD_QUEUE,
MessagePublisher::POPULATE_INDEX_TYPE => MessagePublisher::FAILED_POPULATE_INDEX_QUEUE,
MessagePublisher::FTP_TYPE => MessagePublisher::FAILED_FTP_QUEUE
];
public static $defaultDelayedQueues = [
MessagePublisher::METADATAS_QUEUE => MessagePublisher::DELAYED_METADATAS_QUEUE,
MessagePublisher::SUBDEF_QUEUE => MessagePublisher::DELAYED_SUBDEF_QUEUE
];
public static $defaultLoopTypes = [
MessagePublisher::PULL_ASSETS_TYPE,
MessagePublisher::VALIDATION_REMINDER_TYPE
];
// default message TTL in retry queue in millisecond // default message TTL in retry queue in millisecond
const RETRY_DELAY = 10000; const DEFAULT_RETRY_DELAY_VALUE = 10000;
// default message TTL for some retry queue , 3 minute
const RETRY_LARGE_DELAY = 180000;
// default message TTL in delayed queue in millisecond // default message TTL in delayed queue in millisecond
const DELAY = 5000; const DEFAULT_DELAYED_DELAY_VALUE = 5000;
// max number of retry before a message goes in failed
const DEFAULT_MAX_RETRY_VALUE = 3;
const WITH_NOTHING = 0;
const WITH_RETRY = 1;
const WITH_DELAYED = 2;
const WITH_LOOP = 4;
const BASE_QUEUE = 'base_q';
const BASE_QUEUE_WITH_RETRY = 'base_q_with_retry';
const BASE_QUEUE_WITH_LOOP = 'base_q_with_loop';
const RETRY_QUEUE = 'retry_q';
const LOOP_QUEUE = 'loop_q';
const FAILED_QUEUE = 'failed_q';
const DELAYED_QUEUE = 'delayed_q';
// settings names
const MAX_RETRY = 'max_retry';
const TTL_RETRY = 'ttl_retry';
const TTL_DELAYED = 'ttl_delayed';
const MESSAGES = [
MessagePublisher::ASSETS_INGEST_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::CREATE_RECORD_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::DELETE_RECORD_TYPE => [
'with' => self::WITH_NOTHING,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
],
MessagePublisher::EXPORT_MAIL_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::EXPOSE_UPLOAD_TYPE => [
'with' => self::WITH_NOTHING,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
],
MessagePublisher::FTP_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => 180 * 1000,
],
MessagePublisher::MAIN_QUEUE_TYPE => [
'with' => self::WITH_NOTHING,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
],
MessagePublisher::POPULATE_INDEX_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::PULL_ASSETS_TYPE => [
'with' => self::WITH_LOOP,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::RECORD_EDIT_TYPE => [
'with' => self::WITH_NOTHING,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
],
MessagePublisher::SUBDEF_CREATION_TYPE => [
'with' => self::WITH_RETRY | self::WITH_DELAYED,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
self::TTL_DELAYED => self::DEFAULT_DELAYED_DELAY_VALUE
],
MessagePublisher::SUBTITLE_TYPE => [
'with' => self::WITH_NOTHING,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
],
MessagePublisher::VALIDATION_REMINDER_TYPE => [
'with' => self::WITH_LOOP,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => 7200 * 1000,
],
MessagePublisher::WEBHOOK_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::WRITE_METADATAS_TYPE => [
'with' => self::WITH_RETRY | self::WITH_DELAYED,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
self::TTL_DELAYED => self::DEFAULT_DELAYED_DELAY_VALUE
],
];
private $queues = []; // filled during construct, from msg list, default values and conf
public function __construct(PropertyAccess $conf) public function __construct(PropertyAccess $conf)
{ {
@@ -94,6 +137,165 @@ class AMQPConnection
$this->hostConfig = $conf->get(['workers', 'queue', 'worker-queue'], $defaultConfiguration); $this->hostConfig = $conf->get(['workers', 'queue', 'worker-queue'], $defaultConfiguration);
$this->conf = $conf; $this->conf = $conf;
// fill list of type attributes
foreach (self::MESSAGES as $name => $attr) {
$settings = $attr;
unset($settings['with']);
$this->queues[$name] = [
'Name' => $name,
'QType' => self::BASE_QUEUE,
'default_settings' => $settings, // to be displayed as placeholder
'settings' => $settings, // settings belongs only to base_q
'Exchange' => self::ALCHEMY_EXCHANGE,
];
// a q with retry can fail (after n retry) or loop
if($attr['with'] & self::WITH_RETRY) {
$this->queues[$name]['QType'] = self::BASE_QUEUE_WITH_RETRY; // todo : avoid changing qtype ?
// declare the retry q, cross-link with base q
$retry_name = $name . '_retry';
$this->queues[$name]['RetryQ'] = $retry_name; // link baseq to retryq
$this->queues[$retry_name] = [
'Name' => $retry_name,
'QType' => self::RETRY_QUEUE,
'Exchange' => self::RETRY_ALCHEMY_EXCHANGE,
'BaseQ' => $name, // link retryq back to baseq
];
// declare the failed q, cross-link with base q
$failed_name = $name . '_failed';
$this->queues[$name]['FailedQ'] = $failed_name; // link baseq to failedq
$this->queues[$failed_name] = [
'Name' => $failed_name,
'QType' => self::FAILED_QUEUE,
'Exchange' => self::RETRY_ALCHEMY_EXCHANGE,
'BaseQ' => $name, // link failedq back to baseq
];
}
// a q can be "delayed" to solve "work in progress" lock on records
if($attr['with'] & self::WITH_DELAYED) {
// declare the delayed q, cross-link with base q
$delayed_name = $name . '_delayed';
$this->queues[$name]['DelayedQ'] = $delayed_name; // link baseq to delayedq
$this->queues[$delayed_name] = [
'Name' => $delayed_name,
'QType' => self::DELAYED_QUEUE,
'Exchange' => self::RETRY_ALCHEMY_EXCHANGE,
'BaseQ' => $name, // link delayedq back to baseq
];
}
if($attr['with'] & self::WITH_LOOP) {
$this->queues[$name]['QType'] = self::BASE_QUEUE_WITH_LOOP; // todo : avoid changing qtype ?
// declare the loop q, cross-link with base q
$loop_name = $name . '_loop';
$this->queues[$name]['LoopQ'] = $loop_name; // link baseq to loopq
$this->queues[$loop_name] = [
'Name' => $loop_name,
'QType' => self::LOOP_QUEUE,
'Exchange' => self::RETRY_ALCHEMY_EXCHANGE,
'BaseQ' => $name, // link loopq back to baseq
];
}
}
// inject conf values
foreach($conf->get(['workers', 'queues'], []) as $name => $settings) {
if(!isset($this->queues[$name])) {
throw new Exception(sprintf('undefined queue "%s" in conf', $name));
}
$this->queues[$name]['settings'] = array_merge($this->queues[$name]['settings'], $settings);
}
}
public function getBaseQueueNames()
{
$keys = array_keys(self::MESSAGES);
asort($keys);
return $keys;
}
public function isBaseQueue(string $queueName)
{
return array_key_exists($queueName, self::MESSAGES);
}
public function getBaseQueueName(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName);
return $q['Name'];
}
public function hasRetryQueue(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName);
return array_key_exists('RetryQ', $q);
}
public function getRetryQueueName(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName, 'RetryQ');
return $q['Name'];
}
public function getSetting(string $baseQueueName, string $settingName)
{
$q = $this->getQueue($baseQueueName);
return $q['settings'][$settingName];
}
public function getDefaultSetting(string $baseQueueName, string $settingName)
{
$q = $this->getQueue($baseQueueName);
return $q['default_settings'][$settingName];
}
public function hasDelayedQueue(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName);
return array_key_exists('DelayedQ', $q);
}
public function getDelayedQueueName(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName, 'DelayedQ');
return $q['Name'];
}
public function getFailedQueueName(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName, 'FailedQ');
return $q['Name'];
}
public function hasLoopQueue(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName);
return array_key_exists('LoopQ', $q);
}
public function getLoopQueueName(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName, 'LoopQ');
return $q['Name'];
}
public function getExchange(string $queueName)
{
$q = $this->getQueue($queueName);
return $q['Exchange'];
}
private function getQueue(string $queueName, string $subQueueKey = null)
{
if(!array_key_exists($queueName, $this->queues)) {
throw new Exception(sprintf('undefined queue "%s"', $queueName));
}
if($subQueueKey && !array_key_exists($subQueueKey, $this->queues[$queueName])) {
throw new Exception(sprintf('base queue "%s" has no "%s"', $queueName, $subQueueKey));
}
return $subQueueKey ? $this->queues[$this->queues[$queueName][$subQueueKey]] : $this->queues[$queueName];
} }
public function getConnection() public function getConnection()
@@ -108,8 +310,9 @@ class AMQPConnection
$this->hostConfig['vhost'] $this->hostConfig['vhost']
); );
} catch (\Exception $e) { }
catch (Exception $e) {
// no-op
} }
} }
@@ -127,7 +330,8 @@ class AMQPConnection
} }
return null; return null;
} else { }
else {
return $this->channel; return $this->channel;
} }
} }
@@ -156,106 +360,191 @@ class AMQPConnection
$this->declareExchange(); $this->declareExchange();
} }
if (isset(self::$defaultRetryQueues[$queueName])) { $queue = $this->queues[$queueName];
$this->channel->queue_declare($queueName, false, true, false, false, false, new AMQPTable([ switch($queue['QType']) {
case self::BASE_QUEUE_WITH_RETRY:
$this->queue_declare_and_bind($queueName, self::ALCHEMY_EXCHANGE, [
'x-dead-letter-exchange' => self::RETRY_ALCHEMY_EXCHANGE, // the exchange to which republish a 'dead' message 'x-dead-letter-exchange' => self::RETRY_ALCHEMY_EXCHANGE, // the exchange to which republish a 'dead' message
'x-dead-letter-routing-key' => self::$defaultRetryQueues[$queueName] // the routing key to apply to this 'dead' message 'x-dead-letter-routing-key' => $queue['RetryQ'] // the routing key to apply to this 'dead' message
])); ]);
$this->setQueue($queue['RetryQ']);
$this->channel->queue_bind($queueName, self::ALCHEMY_EXCHANGE, $queueName); break;
case self::BASE_QUEUE_WITH_LOOP:
// declare also the corresponding retry queue $this->queue_declare_and_bind($queueName, self::ALCHEMY_EXCHANGE, [
// use this to delay the delivery of a message to the alchemy-exchange 'x-dead-letter-exchange' => self::RETRY_ALCHEMY_EXCHANGE, // the exchange to which republish a 'dead' message
$this->channel->queue_declare(self::$defaultRetryQueues[$queueName], false, true, false, false, false, new AMQPTable([ 'x-dead-letter-routing-key' => $queue['LoopQ'] // the routing key to apply to this 'dead' message
'x-dead-letter-exchange' => AMQPConnection::ALCHEMY_EXCHANGE, ]);
'x-dead-letter-routing-key' => $queueName, $this->setQueue($queue['LoopQ']);
'x-message-ttl' => $this->getTtlRetryPerRouting($queueName) break;
])); case self::LOOP_QUEUE:
case self::RETRY_QUEUE:
$this->channel->queue_bind(self::$defaultRetryQueues[$queueName], AMQPConnection::RETRY_ALCHEMY_EXCHANGE, self::$defaultRetryQueues[$queueName]); $this->queue_declare_and_bind($queueName, self::RETRY_ALCHEMY_EXCHANGE, [
'x-dead-letter-exchange' => self::ALCHEMY_EXCHANGE,
} elseif (in_array($queueName, self::$defaultRetryQueues)) { 'x-dead-letter-routing-key' => $queue['BaseQ'],
// if it's a retry queue 'x-message-ttl' => (int)$this->queues[$queue['BaseQ']]['settings'][self::TTL_RETRY]
$routing = array_search($queueName, AMQPConnection::$defaultRetryQueues); ]);
$this->channel->queue_declare($queueName, false, true, false, false, false, new AMQPTable([ break;
'x-dead-letter-exchange' => AMQPConnection::ALCHEMY_EXCHANGE, case self::DELAYED_QUEUE:
'x-dead-letter-routing-key' => $routing, $this->queue_declare_and_bind($queueName, self::RETRY_ALCHEMY_EXCHANGE, [
'x-message-ttl' => $this->getTtlRetryPerRouting($routing) 'x-dead-letter-exchange' => self::ALCHEMY_EXCHANGE,
])); 'x-dead-letter-routing-key' => $queue['BaseQ'],
'x-message-ttl' => (int)$this->queues[$queue['BaseQ']]['settings'][self::TTL_DELAYED]
$this->channel->queue_bind($queueName, AMQPConnection::RETRY_ALCHEMY_EXCHANGE, $queueName); ]);
} elseif (in_array($queueName, self::$defaultFailedQueues)) { break;
// if it's a failed queue case self::FAILED_QUEUE:
$this->channel->queue_declare($queueName, false, true, false, false, false); $this->queue_declare_and_bind($queueName, self::RETRY_ALCHEMY_EXCHANGE);
break;
$this->channel->queue_bind($queueName, AMQPConnection::RETRY_ALCHEMY_EXCHANGE, $queueName); case self::BASE_QUEUE:
} elseif (in_array($queueName, self::$defaultDelayedQueues)) { $this->queue_declare_and_bind($queueName, self::ALCHEMY_EXCHANGE);
// if it's a delayed queue break;
$routing = array_search($queueName, AMQPConnection::$defaultDelayedQueues); default:
$this->channel->queue_declare($queueName, false, true, false, false, false, new AMQPTable([ throw new Exception(sprintf('undefined q type "%s', $queueName));
'x-dead-letter-exchange' => AMQPConnection::ALCHEMY_EXCHANGE, break;
'x-dead-letter-routing-key' => $routing,
'x-message-ttl' => $this->getTtlDelayedPerRouting($routing)
]));
$this->channel->queue_bind($queueName, AMQPConnection::RETRY_ALCHEMY_EXCHANGE, $queueName);
} else {
$this->channel->queue_declare($queueName, false, true, false, false, false);
$this->channel->queue_bind($queueName, AMQPConnection::ALCHEMY_EXCHANGE, $queueName);
} }
return $this->channel; return $this->channel;
} }
public function reinitializeQueue(array $queuNames) private function queue_declare_and_bind(string $name, string $exchange, array $arguments = null)
{
try {
$this->channel->queue_declare(
$name,
false, true, false, false, false,
$arguments ? new AMQPTable($arguments) : null
);
$this->channel->queue_bind($name, $exchange, $name);
}
catch (Exception $e) {
// the q exists and arguments don't match, fallback.
// Happens when we try to get the number of messages (getQueueStatus)
// after the settings (e.g. ttl) was changed and the q was not yet re-created
$this->getConnection();
if (isset($this->connection)) {
$this->channel = $this->connection->channel();
}
$this->channel->queue_declare($name,true);
}
}
/**
* purge some queues, delete related retry-q
* nb: called by admin/purgeQueuAction, so a q may be __any kind__ - not only base-types !
*
* @param array $queueNames
* @throws Exception
*/
public function reinitializeQueue(array $queueNames)
{ {
if (!isset($this->channel)) { if (!isset($this->channel)) {
$this->getChannel(); $this->getChannel();
$this->declareExchange(); $this->declareExchange();
} }
foreach ($queuNames as $queuName) {
if (in_array($queuName, self::$defaultQueues)) { foreach ($queueNames as $queueName) {
$this->channel->queue_purge($queuName); // re-inject conf values (some may have changed)
} else { $settings = $this->conf->get(['workers', 'queues', $queueName], []);
$this->channel->queue_delete($queuName); if(array_key_exists($queueName, $this->queues)) {
$this->queues[$queueName]['settings'] = array_merge($this->queues[$queueName]['settings'], $settings);
} }
if (isset(self::$defaultRetryQueues[$queuName])) { if(array_key_exists($queueName, self::MESSAGES)) {
$this->channel->queue_delete(self::$defaultRetryQueues[$queuName]); // base-q
$this->purgeQueue($queueName);
if($this->hasRetryQueue($queueName)) {
$this->deleteQueue($this->getRetryQueueName($queueName));
}
if($this->hasLoopQueue($queueName)) {
$this->deleteQueue($this->getLoopQueueName($queueName));
}
}
else {
// retry, delayed, loop, ... q
$this->deleteQueue($queueName);
} }
$this->setQueue($queuName); $this->setQueue($queueName);
}
}
/**
* delete a queue, fails silently if the q does not exists
*
* @param $queueName
*/
public function deleteQueue($queueName)
{
if (!isset($this->channel)) {
$this->getChannel();
$this->declareExchange();
}
try {
$this->channel->queue_delete($queueName);
}
catch(Exception $e) {
// no-op
}
}
/**
* purge a queue, fails silently if the q does not exists
*
* @param $queueName
*/
public function purgeQueue($queueName)
{
if (!isset($this->channel)) {
$this->getChannel();
$this->declareExchange();
}
try {
$this->channel->queue_purge($queueName);
}
catch(Exception $e) {
// no-op
} }
} }
/** /**
* Get queueName, messageCount, consumerCount of queues * Get queueName, messageCount, consumerCount of queues
* @return array * @return array
* @throws Exception
*/ */
public function getQueuesStatus() public function getQueuesStatus()
{ {
$queuesList = array_merge(
array_values(self::$defaultQueues),
array_values(self::$defaultDelayedQueues),
array_values(self::$defaultRetryQueues),
array_values(self::$defaultFailedQueues)
);
$this->getChannel(); $this->getChannel();
$queuesStatus = []; $queuesStatus = [];
foreach ($queuesList as $queue) { foreach($this->queues as $name => $queue) {
$this->setQueue($queue);
list($queueName, $messageCount, $consumerCount) = $this->channel->queue_declare($queue, true);
$status['queueName'] = $queueName; $this->setQueue($name); // todo : BASE_QUEUE_WITH_RETRY will set both BASE and RETRY Q, so we should skip one of 2
$status['messageCount'] = $messageCount;
$status['consumerCount'] = $consumerCount;
$queuesStatus[] = $status; $this->getConnection();
unset($status); if (isset($this->connection)) {
$this->channel = $this->connection->channel();
} }
try {
list($queueName, $messageCount, $consumerCount) = $this->channel->queue_declare($name, true);
$queuesStatus[$queueName] = [
'queueName' => $queueName,
'exists' => true,
'messageCount' => $messageCount,
'consumerCount' => $consumerCount
];
}
catch (Exception $e) {
// should not happen since "setQueue()" was called
$queuesStatus[$name] = [
'queueName' => $name,
'exists' => false,
'messageCount' => -1,
'consumerCount' => -1
];
}
}
ksort($queuesStatus);
return $queuesStatus; return $queuesStatus;
} }
@@ -265,58 +554,4 @@ class AMQPConnection
$this->channel->close(); $this->channel->close();
$this->connection->close(); $this->connection->close();
} }
/**
* @param $routing
* @return int
*/
private function getTtlRetryPerRouting($routing)
{
$config = $this->conf->get(['workers']);
if ($routing == MessagePublisher::PULL_QUEUE &&
isset($config['pull_assets']) &&
isset($config['pull_assets']['pullInterval']) ) {
// convert in milli second
return (int)($config['pull_assets']['pullInterval']) * 1000;
} elseif ($routing == MessagePublisher::VALIDATION_REMINDER_QUEUE) {
if (isset($config['validationReminder']) &&
isset($config['validationReminder']['interval'])) {
// convert in milli second
return (int)($config['validationReminder']['interval']) * 1000;
}
// default value to 2 hour if not set
return (int) 7200 * 1000;
} elseif (isset($config['retry_queue']) &&
isset($config['retry_queue'][array_search($routing, AMQPConnection::$defaultQueues)])) {
return (int)($config['retry_queue'][array_search($routing, AMQPConnection::$defaultQueues)]);
}
if ($routing == MessagePublisher::FTP_QUEUE) {
return self::RETRY_LARGE_DELAY;
} else {
return self::RETRY_DELAY;
}
}
private function getTtlDelayedPerRouting($routing)
{
$delayed = [
MessagePublisher::METADATAS_QUEUE => 'delayedWriteMeta',
MessagePublisher::SUBDEF_QUEUE => 'delayedSubdef'
];
$config = $this->conf->get(['workers']);
if (isset($config['retry_queue']) && isset($config['retry_queue'][$delayed[$routing]])) {
return (int)$config['retry_queue'][$delayed[$routing]];
}
return self::DELAY;
}
} }

View File

@@ -4,6 +4,7 @@ namespace Alchemy\Phrasea\WorkerManager\Queue;
use Alchemy\Phrasea\WorkerManager\Worker\ProcessPool; use Alchemy\Phrasea\WorkerManager\Worker\ProcessPool;
use Alchemy\Phrasea\WorkerManager\Worker\WorkerInvoker; use Alchemy\Phrasea\WorkerManager\Worker\WorkerInvoker;
use Exception;
use PhpAmqpLib\Channel\AMQPChannel; use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Message\AMQPMessage; use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable; use PhpAmqpLib\Wire\AMQPTable;
@@ -11,8 +12,6 @@ use Ramsey\Uuid\Uuid;
class MessageHandler class MessageHandler
{ {
const MAX_OF_TRY = 3;
private $messagePublisher; private $messagePublisher;
public function __construct(MessagePublisher $messagePublisher) public function __construct(MessagePublisher $messagePublisher)
@@ -20,27 +19,37 @@ class MessageHandler
$this->messagePublisher = $messagePublisher; $this->messagePublisher = $messagePublisher;
} }
public function consume(AMQPConnection $serverConnection, WorkerInvoker $workerInvoker, $argQueueName, $maxProcesses) /**
* called by WorkerExecuteCommand cli
*
* @param AMQPConnection $AMQPConnection
* @param WorkerInvoker $workerInvoker
* @param array|null $argQueueNames
* @param $maxProcesses
*/
public function consume(AMQPConnection $AMQPConnection, WorkerInvoker $workerInvoker, $argQueueNames, $maxProcesses)
{ {
$publisher = $this->messagePublisher;
$channel = $serverConnection->getChannel(); $channel = $AMQPConnection->getChannel();
if ($channel == null) { if ($channel == null) {
// todo : if there is no channel, can we push ?
$this->messagePublisher->pushLog("Can't connect to rabbit, check configuration!", "error"); $this->messagePublisher->pushLog("Can't connect to rabbit, check configuration!", "error");
return ; return ;
} }
$serverConnection->declareExchange(); $AMQPConnection->declareExchange();
// define consume callbacks // define consume callbacks
$callback = function (AMQPMessage $message) use ($channel, $workerInvoker, $publisher) { $publisher = $this->messagePublisher;
$callback = function (AMQPMessage $message) use ($AMQPConnection, $channel, $workerInvoker, $publisher) {
$data = json_decode($message->getBody(), true); $data = json_decode($message->getBody(), true);
$count = 0; $count = 0;
$headers = null;
if ($message->has('application_headers')) { if ($message->has('application_headers')) {
/** @var AMQPTable $headers */ /** @var AMQPTable $headers */
$headers = $message->get('application_headers'); $headers = $message->get('application_headers');
@@ -49,9 +58,10 @@ class MessageHandler
if (isset($headerData['x-death'])) { if (isset($headerData['x-death'])) {
$xDeathHeader = $headerData['x-death']; $xDeathHeader = $headerData['x-death'];
// todo : if there are more than 1 xdeath ? what is $count ?
foreach ($xDeathHeader as $xdeath) { foreach ($xDeathHeader as $xdeath) {
$queue = $xdeath['queue']; $queue = $xdeath['queue'];
if (!in_array($queue, AMQPConnection::$defaultQueues)) { if (!$AMQPConnection->isBaseQueue($queue)) {
continue; continue;
} }
@@ -61,51 +71,45 @@ class MessageHandler
} }
} }
// if message is yet executed 3 times, save the unprocessed message in the corresponding failed queues $msgType = $data['message_type'];
if ($count > self::MAX_OF_TRY && !in_array($data['message_type'], AMQPConnection::$defaultLoopTypes)) {
$this->messagePublisher->publishFailedMessage($data['payload'], $headers, AMQPConnection::$defaultFailedQueues[$data['message_type']]);
$logMessage = sprintf("Rabbit message executed 3 times, it's to be saved in %s , payload >>> %s", if($count > $AMQPConnection->getSetting($msgType, AMQPConnection::MAX_RETRY) && !$AMQPConnection->hasLoopQueue($msgType)) {
AMQPConnection::$defaultFailedQueues[$data['message_type']], $publisher->publishFailedMessage($data['payload'], $headers, $data['message_type']);
$logMessage = sprintf("Rabbit message executed %s times, it's to be saved in %s , payload >>> %s",
$count,
$msgType,
json_encode($data['payload']) json_encode($data['payload'])
); );
$this->messagePublisher->pushLog($logMessage); $publisher->pushLog($logMessage);
$channel->basic_ack($message->delivery_info['delivery_tag']); $channel->basic_ack($message->delivery_info['delivery_tag']);
} else { }
else {
try { try {
$workerInvoker->invokeWorker($data['message_type'], json_encode($data['payload'])); $workerInvoker->invokeWorker($msgType, json_encode($data['payload']));
if (in_array($data['message_type'], AMQPConnection::$defaultLoopTypes)) { if ($AMQPConnection->hasLoopQueue($msgType)) {
// make a loop for the loop type // make a loop for the loop type
$channel->basic_nack($message->delivery_info['delivery_tag']); $channel->basic_nack($message->delivery_info['delivery_tag']);
} else { } else {
$channel->basic_ack($message->delivery_info['delivery_tag']); $channel->basic_ack($message->delivery_info['delivery_tag']);
} }
$oldPayload = $data['payload']; $publisher->pushLog(
$message = $data['message_type'].' to be consumed! >> Payload ::'. json_encode($oldPayload); sprintf('"%s" to be consumed! >> Payload :: %s', $msgType, json_encode($data['payload']))
);
$publisher->pushLog($message); }
} catch (\Exception $e) { catch (Exception $e) {
$channel->basic_nack($message->delivery_info['delivery_tag']); $channel->basic_nack($message->delivery_info['delivery_tag']);
} }
} }
}; };
$prefetchCount = ProcessPool::MAX_PROCESSES; $prefetchCount = $maxProcesses ? $maxProcesses : ProcessPool::MAX_PROCESSES;
foreach($AMQPConnection->getBaseQueueNames() as $queueName) {
if ($maxProcesses) { if (!$argQueueNames || in_array($queueName, $argQueueNames)) {
$prefetchCount = $maxProcesses; $this->runConsumer($queueName, $AMQPConnection, $channel, $prefetchCount, $callback);
}
foreach (AMQPConnection::$defaultQueues as $queueName) {
if ($argQueueName ) {
if (in_array($queueName, $argQueueName)) {
$this->runConsumer($queueName, $serverConnection, $channel, $prefetchCount, $callback);
}
} else {
$this->runConsumer($queueName, $serverConnection, $channel, $prefetchCount, $callback);
} }
} }
} }
@@ -114,9 +118,10 @@ class MessageHandler
{ {
$serverConnection->setQueue($queueName); $serverConnection->setQueue($queueName);
// todo : remove this if !!! move code to a generic place
// initialize validation reminder when starting consumer // initialize validation reminder when starting consumer
if ($queueName == MessagePublisher::VALIDATION_REMINDER_QUEUE) { if ($queueName == MessagePublisher::VALIDATION_REMINDER_TYPE) {
$serverConnection->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_QUEUE]); $serverConnection->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_TYPE]);
$this->messagePublisher->initializeLoopQueue(MessagePublisher::VALIDATION_REMINDER_TYPE); $this->messagePublisher->initializeLoopQueue(MessagePublisher::VALIDATION_REMINDER_TYPE);
} }

View File

@@ -2,6 +2,8 @@
namespace Alchemy\Phrasea\WorkerManager\Queue; namespace Alchemy\Phrasea\WorkerManager\Queue;
use DateTime;
use DateTimeZone;
use Monolog\Logger; use Monolog\Logger;
use PhpAmqpLib\Message\AMQPMessage; use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable; use PhpAmqpLib\Wire\AMQPTable;
@@ -28,107 +30,96 @@ class MessagePublisher
const MAIN_QUEUE_TYPE = 'mainQueue'; const MAIN_QUEUE_TYPE = 'mainQueue';
const MAIN_QUEUE = 'main-queue';
const SUBTITLE_QUEUE = 'subtitle-queue';
// ** ** \\
// worker queue to be consumed, when no ack , it is requeued to the retry queue
const ASSETS_INGEST_QUEUE = 'ingest-queue';
const CREATE_RECORD_QUEUE = 'createrecord-queue';
const DELETE_RECORD_QUEUE = 'deleterecord-queue';
const EXPORT_QUEUE = 'export-queue';
const EXPOSE_UPLOAD_QUEUE = 'exposeupload-queue';
const FTP_QUEUE = 'ftp-queue';
const METADATAS_QUEUE = 'metadatas-queue';
const POPULATE_INDEX_QUEUE = 'populateindex-queue';
const PULL_QUEUE = 'pull-queue';
const RECORD_EDIT_QUEUE = 'recordedit-queue';
const SUBDEF_QUEUE = 'subdef-queue';
const VALIDATION_REMINDER_QUEUE = 'validationReminder-queue';
const WEBHOOK_QUEUE = 'webhook-queue';
// retry queue
// we can use these retry queue with TTL, so when message expires it is requeued to the corresponding worker queue
const RETRY_ASSETS_INGEST_QUEUE = 'retry-ingest-queue';
const RETRY_CREATE_RECORD_QUEUE = 'retry-createrecord-queue';
const RETRY_EXPORT_QUEUE = 'retry-export-queue';
const RETRY_FTP_QUEUE = 'retry-ftp-queue';
const RETRY_METADATAS_QUEUE = 'retry-metadatas-queue';
const RETRY_POPULATE_INDEX_QUEUE = 'retry-populateindex-queue';
const RETRY_SUBDEF_QUEUE = 'retry-subdef-queue';
const RETRY_WEBHOOK_QUEUE = 'retry-webhook-queue';
// use those queue to make a loop on a consumer
const LOOP_PULL_QUEUE = 'loop-pull-queue';
const LOOP_VALIDATION_REMINDER_QUEUE = 'loop-validationReminder-queue';
// all failed queue, if message is treated over 3 times it goes to the failed queue
const FAILED_ASSETS_INGEST_QUEUE = 'failed-ingest-queue';
const FAILED_CREATE_RECORD_QUEUE = 'failed-createrecord-queue';
const FAILED_EXPORT_QUEUE = 'failed-export-queue';
const FAILED_FTP_QUEUE = 'failed-ftp-queue';
const FAILED_METADATAS_QUEUE = 'failed-metadatas-queue';
const FAILED_POPULATE_INDEX_QUEUE = 'failed-populateindex-queue';
const FAILED_SUBDEF_QUEUE = 'failed-subdef-queue';
const FAILED_WEBHOOK_QUEUE = 'failed-webhook-queue';
// delayed queue when record is locked
const DELAYED_SUBDEF_QUEUE = 'delayed-subdef-queue';
const DELAYED_METADATAS_QUEUE = 'delayed-metadatas-queue';
const NEW_RECORD_MESSAGE = 'newrecord'; const NEW_RECORD_MESSAGE = 'newrecord';
/** @var AMQPConnection $serverConnection */ /** @var AMQPConnection $AMQPConnection */
private $serverConnection; private $AMQPConnection;
/** @var Logger */ /** @var Logger */
private $logger; private $logger;
public function __construct(AMQPConnection $serverConnection, LoggerInterface $logger) public function __construct(AMQPConnection $AMQPConnection, LoggerInterface $logger)
{ {
$this->serverConnection = $serverConnection; $this->AMQPConnection = $AMQPConnection;
$this->logger = $logger; $this->logger = $logger;
} }
public function publishMessage(array $payload, $queueName, $retryCount = null, $workerMessage = '') public function publishMessage(array $payload, $queueName)
{ {
// add published timestamp to all message payload $this->AMQPConnection->getBaseQueueName($queueName); // just to throw an exception if q is undefined
$payload['payload']['published'] = time();
$msg = new AMQPMessage(json_encode($payload));
$routing = array_search($queueName, AMQPConnection::$defaultRetryQueues);
if (count($retryCount) && $routing != false) { $this->_publishMessage($payload, $queueName);
}
public function publishRetryMessage(array $payload, string $baseQueueName, $retryCount, $workerMessage)
{
$retryQ = $this->AMQPConnection->getRetryQueueName($baseQueueName);
$headers = null;
if(!is_null($retryCount)) {
// add a message header information // add a message header information
$headers = new AMQPTable([ $headers = new AMQPTable([
'x-death' => [ 'x-death' => [
[ [
'count' => $retryCount, 'count' => $retryCount,
'exchange' => AMQPConnection::ALCHEMY_EXCHANGE, 'exchange' => AMQPConnection::ALCHEMY_EXCHANGE,
'queue' => $routing, 'queue' => $baseQueueName,
'routing-keys' => $routing, 'routing-keys' => $baseQueueName,
'reason' => 'rejected', // rejected is sended like nack 'reason' => 'rejected', // rejected is sended like nack
'time' => new \DateTime('now', new \DateTimeZone('UTC')) 'time' => new DateTime('now', new DateTimeZone('UTC'))
] ]
], ],
'worker-message' => $workerMessage 'worker-message' => $workerMessage
]); ]);
}
$this->_publishMessage($payload, $retryQ, $headers);
}
public function publishDelayedMessage(array $payload, string $baseQueueName)
{
$delayedQ = $this->AMQPConnection->getDelayedQueueName($baseQueueName);
$this->_publishMessage($payload, $delayedQ);
}
public function publishFailedMessage(array $payload, AMQPTable $headers, $baseQueueName)
{
$FailedQ = $this->AMQPConnection->getFailedQueueName($baseQueueName);
$msg = new AMQPMessage(json_encode($payload));
$msg->set('application_headers', $headers);
$channel = $this->AMQPConnection->setQueue($FailedQ);
if ($channel == null) {
$this->pushLog("Can't connect to rabbit, check configuration!", "error");
return ;
}
$channel->basic_publish($msg, AMQPConnection::RETRY_ALCHEMY_EXCHANGE, $FailedQ);
$this->_publishMessage($payload, $FailedQ, $headers);
}
private function _publishMessage(array $payload, $queueName, $headers = null)
{
// add published timestamp to all message payload
$payload['payload']['published'] = time();
$msg = new AMQPMessage(json_encode($payload));
if (!is_null($headers)) {
// add a message header information
$msg->set('application_headers', $headers); $msg->set('application_headers', $headers);
} }
$channel = $this->serverConnection->setQueue($queueName); if (is_null( ($channel = $this->AMQPConnection->setQueue($queueName)) )) {
if ($channel == null) {
$this->pushLog("Can't connect to rabbit, check configuration!", "error"); $this->pushLog("Can't connect to rabbit, check configuration!", "error");
return true; return true;
} }
$exchange = in_array($queueName, AMQPConnection::$defaultQueues) ? AMQPConnection::ALCHEMY_EXCHANGE : AMQPConnection::RETRY_ALCHEMY_EXCHANGE; $exchange = $this->AMQPConnection->getExchange($queueName); // in_array($queueName, AMQPConnection::$defaultQueues) ? AMQPConnection::ALCHEMY_EXCHANGE : AMQPConnection::RETRY_ALCHEMY_EXCHANGE;
$channel->basic_publish($msg, $exchange, $queueName); $channel->basic_publish($msg, $exchange, $queueName);
return true; return true;
@@ -139,16 +130,16 @@ class MessagePublisher
$payload = [ $payload = [
'message_type' => $type, 'message_type' => $type,
'payload' => [ 'payload' => [
'initTimestamp' => new \DateTime('now', new \DateTimeZone('UTC')) 'initTimestamp' => new DateTime('now', new DateTimeZone('UTC'))
] ]
]; ];
$this->publishMessage($payload, AMQPConnection::$defaultQueues[$type]); $this->publishMessage($payload, $type);
} }
public function connectionClose() public function connectionClose()
{ {
$this->serverConnection->connectionClose(); $this->AMQPConnection->connectionClose();
} }
/** /**
@@ -163,18 +154,4 @@ class MessagePublisher
call_user_func(array($this->logger, $method), $message, $context); call_user_func(array($this->logger, $method), $message, $context);
} }
public function publishFailedMessage(array $payload, AMQPTable $headers, $queueName)
{
$msg = new AMQPMessage(json_encode($payload));
$msg->set('application_headers', $headers);
$channel = $this->serverConnection->setQueue($queueName);
if ($channel == null) {
$this->pushLog("Can't connect to rabbit, check configuration!", "error");
return ;
}
$channel->basic_publish($msg, AMQPConnection::RETRY_ALCHEMY_EXCHANGE, $queueName);
}
} }

View File

@@ -24,6 +24,6 @@ class WebhookPublisher implements WebhookPublisherInterface
] ]
]; ];
$this->messagePublisher->publishMessage($payload, MessagePublisher::WEBHOOK_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::WEBHOOK_TYPE);
} }
} }

View File

@@ -33,7 +33,7 @@ class AssetsIngestSubscriber implements EventSubscriberInterface
'payload' => array_merge($event->getData(), ['type' => WorkerRunningJob::TYPE_PUSH]) 'payload' => array_merge($event->getData(), ['type' => WorkerRunningJob::TYPE_PUSH])
]; ];
$this->messagePublisher->publishMessage($payload, MessagePublisher::ASSETS_INGEST_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::ASSETS_INGEST_TYPE);
} }
public function onAssetsCreationFailure(AssetsCreationFailureEvent $event) public function onAssetsCreationFailure(AssetsCreationFailureEvent $event)
@@ -43,9 +43,9 @@ class AssetsIngestSubscriber implements EventSubscriberInterface
'payload' => $event->getPayload() 'payload' => $event->getPayload()
]; ];
$this->messagePublisher->publishMessage( $this->messagePublisher->publishRetryMessage(
$payload, $payload,
MessagePublisher::RETRY_ASSETS_INGEST_QUEUE, MessagePublisher::ASSETS_INGEST_TYPE,
$event->getCount(), $event->getCount(),
$event->getWorkerMessage() $event->getWorkerMessage()
); );
@@ -84,9 +84,9 @@ class AssetsIngestSubscriber implements EventSubscriberInterface
} }
} }
$this->messagePublisher->publishMessage( $this->messagePublisher->publishRetryMessage(
$payload, $payload,
MessagePublisher::RETRY_CREATE_RECORD_QUEUE, MessagePublisher::CREATE_RECORD_TYPE, // todo
$event->getCount(), $event->getCount(),
$event->getWorkerMessage() $event->getWorkerMessage()
); );

View File

@@ -32,7 +32,7 @@ class ExportSubscriber implements EventSubscriberInterface
] ]
]; ];
$this->messagePublisher->publishMessage($payload, MessagePublisher::EXPORT_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::EXPORT_MAIL_TYPE);
} }
public function onExportMailFailure(ExportMailFailureEvent $event) public function onExportMailFailure(ExportMailFailureEvent $event)
@@ -47,9 +47,9 @@ class ExportSubscriber implements EventSubscriberInterface
] ]
]; ];
$this->messagePublisher->publishMessage( $this->messagePublisher->publishRetryMessage(
$payload, $payload,
MessagePublisher::RETRY_EXPORT_QUEUE, MessagePublisher::EXPORT_MAIL_TYPE,
$event->getCount(), $event->getCount(),
$event->getWorkerMessage() $event->getWorkerMessage()
); );
@@ -66,7 +66,7 @@ class ExportSubscriber implements EventSubscriberInterface
$this->messagePublisher->publishMessage( $this->messagePublisher->publishMessage(
$payload, $payload,
MessagePublisher::FTP_QUEUE MessagePublisher::FTP_TYPE
); );
} }

View File

@@ -36,7 +36,7 @@ class ExposeSubscriber implements EventSubscriberInterface
] ]
]; ];
$this->messagePublisher->publishMessage($payload, MessagePublisher::EXPOSE_UPLOAD_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::EXPOSE_UPLOAD_TYPE);
} }
} }

View File

@@ -5,7 +5,6 @@ namespace Alchemy\Phrasea\WorkerManager\Subscriber;
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Core\Event\Record\DeletedEvent; use Alchemy\Phrasea\Core\Event\Record\DeletedEvent;
use Alchemy\Phrasea\Core\Event\Record\DeleteEvent; use Alchemy\Phrasea\Core\Event\Record\DeleteEvent;
use Alchemy\Phrasea\Core\Event\Record\MetadataChangedEvent;
use Alchemy\Phrasea\Core\Event\Record\RecordEvent; use Alchemy\Phrasea\Core\Event\Record\RecordEvent;
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;
@@ -22,6 +21,8 @@ use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
use Alchemy\Phrasea\WorkerManager\Worker\CreateRecordWorker; use Alchemy\Phrasea\WorkerManager\Worker\CreateRecordWorker;
use Alchemy\Phrasea\WorkerManager\Worker\Factory\WorkerFactoryInterface; use Alchemy\Phrasea\WorkerManager\Worker\Factory\WorkerFactoryInterface;
use Alchemy\Phrasea\WorkerManager\Worker\Resolver\TypeBasedWorkerResolver; use Alchemy\Phrasea\WorkerManager\Worker\Resolver\TypeBasedWorkerResolver;
use databox;
use Exception;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RecordSubscriber implements EventSubscriberInterface class RecordSubscriber implements EventSubscriberInterface
@@ -67,7 +68,7 @@ class RecordSubscriber implements EventSubscriberInterface
] ]
]; ];
$this->messagePublisher->publishMessage($payload, MessagePublisher::SUBDEF_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::SUBDEF_CREATION_TYPE);
} }
} }
} }
@@ -87,7 +88,7 @@ class RecordSubscriber implements EventSubscriberInterface
] ]
]; ];
$this->messagePublisher->publishMessage($payload, MessagePublisher::DELETE_RECORD_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::DELETE_RECORD_TYPE);
} }
public function onSubdefinitionCreationFailure(SubdefinitionCreationFailureEvent $event) public function onSubdefinitionCreationFailure(SubdefinitionCreationFailureEvent $event)
@@ -123,14 +124,14 @@ class RecordSubscriber implements EventSubscriberInterface
$em->persist($workerRunningJob); $em->persist($workerRunningJob);
$em->flush(); $em->flush();
$em->commit(); $em->commit();
} catch (\Exception $e) { } catch (Exception $e) {
$em->rollback(); $em->rollback();
} }
} }
$this->messagePublisher->publishMessage( $this->messagePublisher->publishRetryMessage(
$payload, $payload,
MessagePublisher::RETRY_SUBDEF_QUEUE, MessagePublisher::SUBDEF_CREATION_TYPE,
$event->getCount(), $event->getCount(),
$event->getWorkerMessage() $event->getWorkerMessage()
); );
@@ -170,18 +171,19 @@ class RecordSubscriber implements EventSubscriberInterface
]; ];
if ($subdef->is_physically_present()) { if ($subdef->is_physically_present()) {
$this->messagePublisher->publishMessage($payload, MessagePublisher::METADATAS_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::WRITE_METADATAS_TYPE);
} else { }
$logMessage = sprintf("Subdef %s is not physically present! to be passed in the %s ! payload >>> %s", else {
$logMessage = sprintf('Subdef "%s" is not physically present! to be passed in the retry q of "%s" ! payload >>> %s',
$subdef->get_name(), $subdef->get_name(),
MessagePublisher::RETRY_METADATAS_QUEUE, MessagePublisher::WRITE_METADATAS_TYPE,
json_encode($payload) json_encode($payload)
); );
$this->messagePublisher->pushLog($logMessage); $this->messagePublisher->pushLog($logMessage);
$this->messagePublisher->publishMessage( $this->messagePublisher->publishRetryMessage(
$payload, $payload,
MessagePublisher::RETRY_METADATAS_QUEUE, MessagePublisher::WRITE_METADATAS_TYPE,
2, 2,
'Subdef is not physically present!' 'Subdef is not physically present!'
); );
@@ -215,10 +217,10 @@ class RecordSubscriber implements EventSubscriberInterface
] ]
]; ];
$logMessage = sprintf("Subdef %s write meta failed, error : %s ! to be passed in the %s ! payload >>> %s", $logMessage = sprintf('Subdef "%s" write meta failed, error : "%s" ! to be passed in the retry q of "%s" ! payload >>> %s',
$event->getSubdefName(), $event->getSubdefName(),
$event->getWorkerMessage(), $event->getWorkerMessage(),
MessagePublisher::RETRY_METADATAS_QUEUE, MessagePublisher::WRITE_METADATAS_TYPE,
json_encode($payload) json_encode($payload)
); );
$this->messagePublisher->pushLog($logMessage); $this->messagePublisher->pushLog($logMessage);
@@ -243,14 +245,14 @@ class RecordSubscriber implements EventSubscriberInterface
$em->persist($workerRunningJob); $em->persist($workerRunningJob);
$em->flush(); $em->flush();
$em->commit(); $em->commit();
} catch (\Exception $e) { } catch (Exception $e) {
$em->rollback(); $em->rollback();
} }
} }
$this->messagePublisher->publishMessage( $this->messagePublisher->publishRetryMessage(
$payload, $payload,
MessagePublisher::RETRY_METADATAS_QUEUE, MessagePublisher::WRITE_METADATAS_TYPE,
$event->getCount(), $event->getCount(),
$event->getWorkerMessage() $event->getWorkerMessage()
); );
@@ -276,7 +278,7 @@ class RecordSubscriber implements EventSubscriberInterface
] ]
]; ];
$this->messagePublisher->publishMessage($payload, MessagePublisher::METADATAS_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::WRITE_METADATAS_TYPE);
} }
} }
@@ -294,19 +296,27 @@ class RecordSubscriber implements EventSubscriberInterface
] ]
]; ];
$this->messagePublisher->publishMessage($payload, MessagePublisher::RECORD_EDIT_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::RECORD_EDIT_TYPE);
} }
public static function getSubscribedEvents() public static function getSubscribedEvents()
{ {
return [ return [
/** @uses onRecordCreated */
RecordEvents::CREATED => 'onRecordCreated', RecordEvents::CREATED => 'onRecordCreated',
/** @uses onSubdefinitionCreate */
RecordEvents::SUBDEFINITION_CREATE => 'onSubdefinitionCreate', RecordEvents::SUBDEFINITION_CREATE => 'onSubdefinitionCreate',
/** @uses onDelete */
RecordEvents::DELETE => 'onDelete', RecordEvents::DELETE => 'onDelete',
/** @uses onSubdefinitionCreationFailure */
WorkerEvents::SUBDEFINITION_CREATION_FAILURE => 'onSubdefinitionCreationFailure', WorkerEvents::SUBDEFINITION_CREATION_FAILURE => 'onSubdefinitionCreationFailure',
/** @uses onRecordsWriteMeta */
WorkerEvents::RECORDS_WRITE_META => 'onRecordsWriteMeta', WorkerEvents::RECORDS_WRITE_META => 'onRecordsWriteMeta',
/** @uses onStoryCreateCover */
WorkerEvents::STORY_CREATE_COVER => 'onStoryCreateCover', WorkerEvents::STORY_CREATE_COVER => 'onStoryCreateCover',
/** @uses onSubdefinitionWritemeta */
WorkerEvents::SUBDEFINITION_WRITE_META => 'onSubdefinitionWritemeta', WorkerEvents::SUBDEFINITION_WRITE_META => 'onSubdefinitionWritemeta',
/** @uses onRecordEditInWorker */
WorkerEvents::RECORD_EDIT_IN_WORKER => 'onRecordEditInWorker' WorkerEvents::RECORD_EDIT_IN_WORKER => 'onRecordEditInWorker'
]; ];
} }
@@ -322,12 +332,12 @@ class RecordSubscriber implements EventSubscriberInterface
} }
/** /**
* @param \databox $databox * @param databox $databox
* @param string $subdefType * @param string $subdefType
* @param string $subdefName * @param string $subdefName
* @return bool * @return bool
*/ */
private function isSubdefMetadataUpdateRequired(\databox $databox, $subdefType, $subdefName) private function isSubdefMetadataUpdateRequired(databox $databox, $subdefType, $subdefName)
{ {
if ($databox->get_subdef_structure()->hasSubdef($subdefType, $subdefName)) { if ($databox->get_subdef_structure()->hasSubdef($subdefType, $subdefName)) {
return $databox->get_subdef_structure()->get_subdef($subdefType, $subdefName)->isMetadataUpdateRequired(); return $databox->get_subdef_structure()->get_subdef($subdefType, $subdefName)->isMetadataUpdateRequired();

View File

@@ -40,7 +40,7 @@ class SearchengineSubscriber implements EventSubscriberInterface
] ]
]; ];
$this->messagePublisher->publishMessage($payload, MessagePublisher::POPULATE_INDEX_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::POPULATE_INDEX_TYPE);
} }
} }
@@ -83,9 +83,9 @@ class SearchengineSubscriber implements EventSubscriberInterface
} }
} }
$this->messagePublisher->publishMessage( $this->messagePublisher->publishRetryMessage(
$payload, $payload,
MessagePublisher::RETRY_POPULATE_INDEX_QUEUE, MessagePublisher::POPULATE_INDEX_TYPE,
$event->getCount(), $event->getCount(),
$event->getWorkerMessage() $event->getWorkerMessage()
); );

View File

@@ -65,7 +65,7 @@ class SubtitleSubscriber implements EventSubscriberInterface
'payload' => $data 'payload' => $data
]; ];
$this->messagePublisher->publishMessage($payload, MessagePublisher::MAIN_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::MAIN_QUEUE_TYPE);
} catch (\Exception $e) { } catch (\Exception $e) {
$em->rollback(); $em->rollback();
} }

View File

@@ -29,9 +29,9 @@ class WebhookSubscriber implements EventSubscriberInterface
] ]
]; ];
$this->messagePublisher->publishMessage( $this->messagePublisher->publishRetryMessage(
$payload, $payload,
MessagePublisher::RETRY_WEBHOOK_QUEUE, MessagePublisher::WEBHOOK_TYPE,
$event->getCount(), $event->getCount(),
$event->getWorkerMessage() $event->getWorkerMessage()
); );

View File

@@ -2,8 +2,8 @@
namespace Alchemy\Phrasea\WorkerManager\Worker; namespace Alchemy\Phrasea\WorkerManager\Worker;
use Alchemy\Phrasea\Application\Helper\EntityManagerAware;
use Alchemy\Phrasea\Application as PhraseaApplication; use Alchemy\Phrasea\Application as PhraseaApplication;
use Alchemy\Phrasea\Application\Helper\EntityManagerAware;
use Alchemy\Phrasea\Model\Entities\StoryWZ; use Alchemy\Phrasea\Model\Entities\StoryWZ;
use Alchemy\Phrasea\Model\Entities\WorkerRunningJob; use Alchemy\Phrasea\Model\Entities\WorkerRunningJob;
use Alchemy\Phrasea\Model\Repositories\UserRepository; use Alchemy\Phrasea\Model\Repositories\UserRepository;
@@ -80,7 +80,7 @@ class AssetsIngestWorker implements WorkerInterface
'commit_id' => $payload['commit_id'] 'commit_id' => $payload['commit_id']
]; ];
$this->messagePublisher->publishMessage($createRecordMessage, MessagePublisher::CREATE_RECORD_QUEUE); $this->messagePublisher->publishMessage($createRecordMessage, MessagePublisher::CREATE_RECORD_TYPE);
/** @var WorkerRunningJob $workerRunningJob */ /** @var WorkerRunningJob $workerRunningJob */
$workerRunningJob = $this->repoWorkerJob->findOneBy([ $workerRunningJob = $this->repoWorkerJob->findOneBy([

View File

@@ -5,7 +5,6 @@ namespace Alchemy\Phrasea\WorkerManager\Worker;
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Application\Helper\NotifierAware; use Alchemy\Phrasea\Application\Helper\NotifierAware;
use Alchemy\Phrasea\Core\LazyLocator; use Alchemy\Phrasea\Core\LazyLocator;
use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Alchemy\Phrasea\Model\Entities\FtpExport; use Alchemy\Phrasea\Model\Entities\FtpExport;
use Alchemy\Phrasea\Model\Entities\FtpExportElement; use Alchemy\Phrasea\Model\Entities\FtpExportElement;
use Alchemy\Phrasea\Model\Entities\WorkerRunningJob; use Alchemy\Phrasea\Model\Entities\WorkerRunningJob;
@@ -371,9 +370,9 @@ class FtpWorker implements WorkerInterface
'payload' => $payload 'payload' => $payload
]; ];
$this->app['alchemy_worker.message.publisher']->publishMessage( $this->getMessagePublisher()->publishRetryMessage(
$fullPayload, $fullPayload,
MessagePublisher::RETRY_FTP_QUEUE, MessagePublisher::FTP_TYPE,
$count, $count,
$workerMessage $workerMessage
); );
@@ -503,4 +502,13 @@ class FtpWorker implements WorkerInterface
{ {
return $this->app['repo.ftp-exports']; return $this->app['repo.ftp-exports'];
} }
/**
* @return MessagePublisher
*/
private function getMessagePublisher()
{
return $this->app['alchemy_worker.message.publisher'];
}
} }

View File

@@ -29,7 +29,7 @@ class MainQueueWorker implements WorkerInterface
switch ($payload['type']) { switch ($payload['type']) {
case MessagePublisher::SUBTITLE_TYPE: case MessagePublisher::SUBTITLE_TYPE:
$queue = MessagePublisher::SUBTITLE_QUEUE; $queue = MessagePublisher::SUBTITLE_TYPE;
$messageType = $payload['type']; $messageType = $payload['type'];
unset($payload['type']); unset($payload['type']);

View File

@@ -82,7 +82,7 @@ class PullAssetsWorker implements WorkerInterface
] ]
]; ];
$this->messagePublisher->publishMessage($payload, MessagePublisher::ASSETS_INGEST_QUEUE); $this->messagePublisher->publishMessage($payload, MessagePublisher::ASSETS_INGEST_TYPE);
} }
} }

View File

@@ -75,7 +75,7 @@ class SubdefCreationWorker implements WorkerInterface
if (!$canCreateSubdef) { if (!$canCreateSubdef) {
// the file is in used to write meta // the file is in used to write meta
$this->messagePublisher->publishMessage($message, MessagePublisher::DELAYED_SUBDEF_QUEUE); $this->messagePublisher->publishDelayedMessage($message, MessagePublisher::SUBDEF_CREATION_TYPE);
return ; return ;
} }
@@ -178,7 +178,10 @@ class SubdefCreationWorker implements WorkerInterface
// checking ended // checking ended
// order to write meta for the subdef if needed // order to write meta for the subdef if needed
$this->dispatcher->dispatch(WorkerEvents::SUBDEFINITION_WRITE_META, new SubdefinitionWritemetaEvent($record, $payload['subdefName'])); $this->dispatcher->dispatch(WorkerEvents::SUBDEFINITION_WRITE_META, new SubdefinitionWritemetaEvent(
$record,
$payload['subdefName'])
);
$this->subdefGenerator->setLogger($oldLogger); $this->subdefGenerator->setLogger($oldLogger);

View File

@@ -170,7 +170,11 @@ class WebhookWorker implements WorkerInterface
$this->messagePublisher->pushLog($workerMessage); $this->messagePublisher->pushLog($workerMessage);
// count = 0 mean do not retry because no api application defined // count = 0 mean do not retry because no api application defined
$this->dispatch(WorkerEvents::WEBHOOK_DELIVER_FAILURE, new WebhookDeliverFailureEvent($webhookevent->getId(), $workerMessage, 0)); $this->dispatch(WorkerEvents::WEBHOOK_DELIVER_FAILURE, new WebhookDeliverFailureEvent(
$webhookevent->getId(),
$workerMessage,
0)
);
return; return;
} }
@@ -235,7 +239,7 @@ class WebhookWorker implements WorkerInterface
$this->messagePublisher->publishFailedMessage( $this->messagePublisher->publishFailedMessage(
$payload, $payload,
new AMQPTable(['worker-message' => $e->getMessage()]), new AMQPTable(['worker-message' => $e->getMessage()]),
MessagePublisher::FAILED_WEBHOOK_QUEUE MessagePublisher::WEBHOOK_TYPE
); );
} }
} }

View File

@@ -12,6 +12,8 @@ use Alchemy\Phrasea\Model\Repositories\WorkerRunningJobRepository;
use Alchemy\Phrasea\WorkerManager\Event\SubdefinitionWritemetaEvent; use Alchemy\Phrasea\WorkerManager\Event\SubdefinitionWritemetaEvent;
use Alchemy\Phrasea\WorkerManager\Event\WorkerEvents; use Alchemy\Phrasea\WorkerManager\Event\WorkerEvents;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher; use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
use DateTime;
use Exception;
use Monolog\Logger; use Monolog\Logger;
use PHPExiftool\Driver\Metadata\Metadata; use PHPExiftool\Driver\Metadata\Metadata;
use PHPExiftool\Driver\Metadata\MetadataBag; use PHPExiftool\Driver\Metadata\MetadataBag;
@@ -21,6 +23,7 @@ use PHPExiftool\Driver\Value\Multi;
use PHPExiftool\Exception\TagUnknown; use PHPExiftool\Exception\TagUnknown;
use PHPExiftool\Writer; use PHPExiftool\Writer;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use record_adapter;
class WriteMetadatasWorker implements WorkerInterface class WriteMetadatasWorker implements WorkerInterface
{ {
@@ -73,7 +76,7 @@ class WriteMetadatasWorker implements WorkerInterface
if (!$canWriteMeta) { if (!$canWriteMeta) {
// the file is in used to generate subdef // the file is in used to generate subdef
$this->messagePublisher->publishMessage($message, MessagePublisher::DELAYED_METADATAS_QUEUE); $this->messagePublisher->publishDelayedMessage($message, MessagePublisher::WRITE_METADATAS_TYPE);
return ; return ;
} }
@@ -113,7 +116,7 @@ class WriteMetadatasWorker implements WorkerInterface
$em->beginTransaction(); $em->beginTransaction();
try { try {
$date = new \DateTime(); $date = new DateTime();
$workerRunningJob = new WorkerRunningJob(); $workerRunningJob = new WorkerRunningJob();
$workerRunningJob $workerRunningJob
->setDataboxId($databoxId) ->setDataboxId($databoxId)
@@ -129,14 +132,17 @@ class WriteMetadatasWorker implements WorkerInterface
$em->flush(); $em->flush();
$em->commit(); $em->commit();
} catch (\Exception $e) { } catch (Exception $e) {
$em->rollback(); $em->rollback();
$this->logger->error("Error persisting WorkerRunningJob !");
return ;
} }
} }
try { try {
$subdef = $record->get_subdef($payload['subdefName']); $subdef = $record->get_subdef($payload['subdefName']);
} catch (\Exception $e) { } catch (Exception $e) {
$workerMessage = "Exception catched when try to get subdef " .$payload['subdefName']. " from DB for the recordID: " .$recordId; $workerMessage = "Exception catched when try to get subdef " .$payload['subdefName']. " from DB for the recordID: " .$recordId;
$this->logger->error($workerMessage); $this->logger->error($workerMessage);
@@ -221,7 +227,7 @@ class WriteMetadatasWorker implements WorkerInterface
try { try {
$value = self::fixDate($value); // will return NULL if the date is not valid $value = self::fixDate($value); // will return NULL if the date is not valid
} }
catch (\Exception $e) { catch (Exception $e) {
$value = null; // do NOT write back to iptc $value = null; // do NOT write back to iptc
} }
} }
@@ -230,7 +236,7 @@ class WriteMetadatasWorker implements WorkerInterface
$value = new Mono($value); $value = new Mono($value);
} }
} }
} catch(\Exception $e) { } catch(Exception $e) {
// the field is not set in the record, erase it // the field is not set in the record, erase it
if ($fieldStructure->is_multi()) { if ($fieldStructure->is_multi()) {
$value = new Multi(array('')); $value = new Multi(array(''));
@@ -260,8 +266,8 @@ class WriteMetadatasWorker implements WorkerInterface
$this->writer->write($subdef->getRealPath(), $metadata); $this->writer->write($subdef->getRealPath(), $metadata);
$this->messagePublisher->pushLog(sprintf('meta written for sbasid=%1$d - recordid=%2$d (%3$s)', $databox->get_sbas_id(), $recordId, $subdef->get_name() )); $this->messagePublisher->pushLog(sprintf('meta written for sbasid=%1$d - recordid=%2$d (%3$s)', $databox->get_sbas_id(), $recordId, $subdef->get_name() ));
} catch (\Exception $e) { } catch (Exception $e) {
$workerMessage = sprintf('meta NOT written for sbasid=%1$d - recordid=%2$d (%3$s) because "%s"', $databox->get_sbas_id(), $recordId, $subdef->get_name() , $e->getMessage()); $workerMessage = sprintf('meta NOT written for sbasid=%1$d - recordid=%2$d (%3$s) because "%4$s"', $databox->get_sbas_id(), $recordId, $subdef->get_name() , $e->getMessage());
$this->logger->error($workerMessage); $this->logger->error($workerMessage);
$count = isset($payload['count']) ? $payload['count'] + 1 : 2 ; $count = isset($payload['count']) ? $payload['count'] + 1 : 2 ;
@@ -297,11 +303,11 @@ class WriteMetadatasWorker implements WorkerInterface
$em->beginTransaction(); $em->beginTransaction();
try { try {
$workerRunningJob->setStatus(WorkerRunningJob::FINISHED); $workerRunningJob->setStatus(WorkerRunningJob::FINISHED);
$workerRunningJob->setFinished(new \DateTime('now')); $workerRunningJob->setFinished(new DateTime('now'));
$em->persist($workerRunningJob); $em->persist($workerRunningJob);
$em->flush(); $em->flush();
$em->commit(); $em->commit();
} catch (\Exception $e) { } catch (Exception $e) {
$em->rollback(); $em->rollback();
} }
@@ -314,7 +320,7 @@ class WriteMetadatasWorker implements WorkerInterface
return str_replace("\0", "", $value); return str_replace("\0", "", $value);
} }
private function updateJeton(\record_adapter $record) private function updateJeton(record_adapter $record)
{ {
$connection = $record->getDatabox()->get_connection(); $connection = $record->getDatabox()->get_connection();
@@ -344,16 +350,16 @@ class WriteMetadatasWorker implements WorkerInterface
$a = explode(';', preg_replace('/\D+/', ';', trim($value))); $a = explode(';', preg_replace('/\D+/', ';', trim($value)));
switch (count($a)) { switch (count($a)) {
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]);
$date = $date->format('Y-m-d H:i:s'); $date = $date->format('Y-m-d H:i:s');
break; break;
case 6: // yyyy;mm;dd;hh;mm;ss case 6: // yyyy;mm;dd;hh;mm;ss
$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]);
$date = $date->format('Y-m-d H:i:s'); $date = $date->format('Y-m-d H:i:s');
break; break;
} }
} }
catch (\Exception $e) { catch (Exception $e) {
$date = null; $date = null;
} }

View File

@@ -0,0 +1,119 @@
<?php
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
class patch_413_PHRAS_3282 implements patchInterface
{
const OLDQ2NEWQ_ttl_retry = [
'assetsIngest' => MessagePublisher::ASSETS_INGEST_TYPE,
'createRecord' => MessagePublisher::CREATE_RECORD_TYPE,
'deleteRecord' => MessagePublisher::DELETE_RECORD_TYPE,
'exportMail' => MessagePublisher::EXPORT_MAIL_TYPE,
'exposeUpload' => MessagePublisher::EXPOSE_UPLOAD_TYPE,
'ftp' => MessagePublisher::FTP_TYPE,
'populateIndex' => MessagePublisher::POPULATE_INDEX_TYPE,
'pullAssets' => MessagePublisher::PULL_ASSETS_TYPE,
'recordEdit' => MessagePublisher::RECORD_EDIT_TYPE,
'subdefCreation' => MessagePublisher::SUBDEF_CREATION_TYPE,
'validationReminder' => MessagePublisher::VALIDATION_REMINDER_TYPE,
'writeMetadatas' => MessagePublisher::WRITE_METADATAS_TYPE,
'webhook' => MessagePublisher::WEBHOOK_TYPE,
];
const OLDQ2NEWQ_ttl_delayed = [
'delayedSubdef' => MessagePublisher::SUBDEF_CREATION_TYPE,
'delayedWriteMeta' => MessagePublisher::WRITE_METADATAS_TYPE,
];
/** @var string */
private $release = '4.1.3';
/** @var array */
private $concern = [base::APPLICATION_BOX, base::DATA_BOX];
/**
* {@inheritdoc}
*/
public function get_release()
{
return $this->release;
}
/**
* {@inheritdoc}
*/
public function getDoctrineMigrations()
{
return [];
}
/**
* {@inheritdoc}
*/
public function require_all_upgrades()
{
return false;
}
/**
* {@inheritdoc}
*/
public function concern()
{
return $this->concern;
}
/**
* {@inheritdoc}
*/
public function apply(base $base, Application $app)
{
if ($base->get_base_type() === base::DATA_BOX) {
$this->patch_databox($base, $app);
}
elseif ($base->get_base_type() === base::APPLICATION_BOX) {
$this->patch_appbox($base, $app);
}
return true;
}
private function patch_databox(base $databox, Application $app)
{
}
private function patch_appbox(base $databox, Application $app)
{
/** @var PropertyAccess $conf */
$conf = $app['conf'];
// --------------------------------------------
// PHRAS-3282_refacto-some-code-on-workers_MASTER
// patch workers settings
// --------------------------------------------
foreach(self::OLDQ2NEWQ_ttl_retry as $old=>$new) {
if(($v = $conf->get(['workers', 'retry_queue', $old], null)) !== null) {
$conf->set(['workers', 'queues', $new, 'ttl_retry'], $v);
}
}
foreach(self::OLDQ2NEWQ_ttl_delayed as $old=>$new) {
if(($v = $conf->get(['workers', 'retry_queue', $old], null)) !== null) {
$conf->set(['workers', 'queues', $new, 'ttl_delayed'], $v);
}
}
if(($v = $conf->get(['workers', 'pull_assets', 'pullInterval'], null)) !== null) {
$conf->set(['workers', 'queues', MessagePublisher::PULL_ASSETS_TYPE, 'ttl_retry'], $v * 1000);
}
if(($v = $conf->get(['workers', 'validationReminder', 'interval'], null)) !== null) {
$conf->set(['workers', 'queues', MessagePublisher::VALIDATION_REMINDER_TYPE, 'ttl_retry'], $v * 1000);
}
$conf->remove(['workers', 'retry_queue']);
$conf->remove(['workers', 'pull_assets']);
$conf->remove(['workers', 'validationReminder']);
}
}

View File

@@ -270,8 +270,7 @@ class record_preview extends record_adapter
$this->title .= parent::get_title($options); $this->title .= parent::get_title($options);
break; break;
case "BASK": case "BASK":
$this->title .= $this->name . ' - ' . parent::get_title($options) $this->title .= $this->name . ' (' . $this->getNumber() . '/' . $this->total . ') - ' . parent::get_title($options);
. ' (' . $this->getNumber() . '/' . $this->total . ') ';
break; break;
case "REG": case "REG":
$title = parent::get_title($options); $title = parent::get_title($options);

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-12-16T08:29:44Z" source-language="en" target-language="de" datatype="plaintext" original="not.available"> <file date="2021-01-13T14:26:33Z" 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-12-16T08:30:32Z" source-language="en" target-language="en" datatype="plaintext" original="not.available"> <file date="2021-01-13T14:27:16Z" 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-12-16T08:31:21Z" source-language="en" target-language="fr" datatype="plaintext" original="not.available"> <file date="2021-01-13T14:28:04Z" 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-12-16T08:32:13Z" source-language="en" target-language="nl" datatype="plaintext" original="not.available"> <file date="2021-01-13T14:28:55Z" 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

@@ -10,7 +10,7 @@
{{ "admin::workermanager:tab:configuration: title" | trans }} {{ "admin::workermanager:tab:configuration: title" | trans }}
</a> </a>
</li> </li>
<li class="worker-info active" role="presentation"> <li class="worker-info" role="presentation">
<a href="#worker-info" aria-controls="worker-info" role="tab" data-toggle="tab" data-url="/admin/worker-manager/info"> <a href="#worker-info" aria-controls="worker-info" role="tab" data-toggle="tab" data-url="/admin/worker-manager/info">
{{ 'admin::workermanager:tab:workerinfo: title' |trans }} {{ 'admin::workermanager:tab:workerinfo: title' |trans }}
</a> </a>
@@ -56,9 +56,7 @@
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
<div role="tabpanel" class="tab-pane fade" id="worker-configuration"></div> <div role="tabpanel" class="tab-pane fade" id="worker-configuration"></div>
<div role="tabpanel" class="tab-pane fade in active" id="worker-info"> <div role="tabpanel" class="tab-pane fade" id="worker-info"></div>
{% include "admin/worker-manager/worker_info.html.twig" %}
</div>
<div role="tabpanel" class="tab-pane fade" id="worker-searchengine"></div> <div role="tabpanel" class="tab-pane fade" id="worker-searchengine"></div>
<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-ftp"></div> <div role="tabpanel" class="tab-pane fade" id="worker-ftp"></div>
@@ -101,6 +99,10 @@
}); });
} }
}); });
var sel = '#configurationTabs li.{{ _fragment }} a';
$(sel).click();
</script> </script>
{% else %} {% else %}
<h1 class="alert alert-danger"> <h1 class="alert alert-danger">

View File

@@ -1,58 +1,38 @@
{% form_theme form 'form_table_layout.html.twig' %}
{% form_theme form.queues 'bootstrap_3_horizontal_layout.html.twig' %}
<h1>{{ 'admin::workermanager:tab:workerconfig: title' |trans }}</h1> <h1>{{ 'admin::workermanager:tab:workerconfig: title' |trans }}</h1>
<p class="alert alert-danger"> <p class="alert alert-danger">
{{ 'admin::workermanager:tab:workerconfig: warning' |trans }} {{ 'admin::workermanager:tab:workerconfig: warning' |trans }}
</p> </p>
<p>{{ 'admin::workermanager:tab:workerconfig: Set up the delay between two attempts per queue! (if not set, default 10000 millisecond)' |trans }}</p>
{{ form_start(form, {'action': path('worker_admin_configuration')}) }} {{ form_start(form, {'action': path('worker_admin_configuration')}) }}
<div class="control-group"> <table>
{{ form_row(form.assetsIngest) }} {% for child in form.queues.children %}
</div> <tr>
<td colspan="4">
<div class="control-group"> <hr/>
{{ form_row(form.createRecord) }} {{ form_label(child) }}
</div> {{ form_errors(child) }}
</td>
<div class="control-group"> </tr>
{{ form_row(form.subdefCreation) }} <tr>
</div> {% for child2 in child.children %}
<td>
<div class="control-group"> {{ form_row(child2) }}
{{ form_row(form.writeMetadatas) }} </td>
</div> {% endfor %}
{{ form_widget(child) }}
<div class="control-group"> </tr>
{{ form_row(form.webhook) }} {% endfor %}
</div> </table>
<br/>
<div class="control-group"> <br/>
{{ form_row(form.exportMail) }}
</div>
<div class="control-group">
{{ form_row(form.populateIndex) }}
</div>
<div class="control-group">
{{ form_row(form.ftp) }}
</div>
<h3> {{ 'admin::workermanager:tab:workerconfig: Config Worker queue delayed' |trans }}</h3>
<p> {{ 'admin::workermanager:tab:workerconfig: if not set ,default 5000 millisecond' |trans }}</p>
<div class="control-group">
{{ form_row(form.delayedSubdef) }}
</div>
<div class="control-group">
{{ form_row(form.delayedWriteMeta) }}
</div>
<div class="control-group">
<input type="submit" class="btn btn-primary" value={{ "admin::workermanager:tab:workerconfig:Apply in queue"|trans }} />
</div>
{{ form_widget(form) }}
{{ form_end(form) }} {{ form_end(form) }}
<br/>

View File

@@ -1,53 +1,40 @@
<h1>{{ 'admin::workermanager:tab:pullassets: description' |trans }}</h1> <h1>{{ 'admin::workermanager:tab:pullassets: description' |trans }}</h1>
{{ form_start(form, {'action': path('worker_admin_pullAssets')}) }} {{ form_start(form, {'action': path('worker_admin_pullAssets')}) }}
{{ form_widget(form) }}
<br/>
<br/>
<br/>
<div class="control-group"> <div class="control-group">
{{ form_row(form.UploaderApiBaseUri) }} {% if(running) %}
<button type="submit" value="stop" class="btn btn-danger stop-pull-assets">{{ 'Stop' | trans }}</button>
{% else %}
<button type="submit" value="start" class="btn btn-primary start-pull-assets">{{ "Start"|trans }}</button>
{% endif %}
</div> </div>
<div class="control-group">
{{ form_row(form.clientSecret) }}
</div>
<div class="control-group">
{{ form_row(form.clientId) }}
</div>
<div class="control-group">
{{ form_row(form.pullInterval) }}
</div>
<div class="control-group">
<button class="btn btn-primary start-pull-mode">{{ "admin::workermanager:tab:pullassets: Initialize pull assets"|trans }}</button>
<button class="btn btn-danger stop-pull-mode">{{ 'admin::workermanager:tab:pullassets: Stop pull assets' | trans }}</button>
</div>
{{ form_end(form) }} {{ form_end(form) }}
<script type="text/javascript"> <script type="text/javascript">
$("#worker-pull-assets").on('click', '.stop-pull-mode', function(e) { $("#worker-pull-assets").on('click', 'BUTTON[type=submit]', function(e, z) {
e.preventDefault(); e.preventDefault();
if (confirm("Warning! You are about to stop pull mode!")) { var button = $(e.target);
$.ajax({ var form = button.closest("FORM");
type: "POST", $("INPUT.act", form).val(button.val()); // "save", "start" or "stop"
url: "/admin/worker-manager/purge-queue",
dataType: 'json', var ok = true;
data : { {% if(running) %}
queueName : "pull-queue" if(button.val() === 'save') {
}, // saving will empty the queue, "stopping"
success: function (data) { ok = confirm("Warning! Changing the settings will stop pulling assets!")
console.log(data); }
return false; {% endif %}
if(button.val() === 'stop') {
// saving will empty the queue, "stopping"
ok = confirm("Warning! You are about to stop pulling assets!")
}
if(ok) {
form.submit();
} }
}); });
}
});
$("#worker-pull-assets").on('click', '.start-pull-mode', function(e) {
e.preventDefault();
$('form[name="worker_pullAssets"]').submit();
});
</script> </script>

View File

@@ -12,6 +12,7 @@
<th>{{ 'admin::workermanager:tab:queueMonitor: Message count' |trans }}</th> <th>{{ 'admin::workermanager:tab:queueMonitor: Message count' |trans }}</th>
<th>{{ 'admin::workermanager:tab:queueMonitor: Consumer count' |trans }}</th> <th>{{ 'admin::workermanager:tab:queueMonitor: Consumer count' |trans }}</th>
<th></th> <th></th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody class="queue-list"> <tbody class="queue-list">
@@ -19,12 +20,15 @@
{% endif %} {% endif %}
{% for queueStatus in queuesStatus %} {% for queueStatus in queuesStatus %}
<tr> <tr>
<th>{{ queueStatus.queueName }}</th> <td>{{ queueStatus.queueName }}</td>
<td>{{ queueStatus.messageCount }}</td> <td>{{ queueStatus.messageCount }}</td>
<td>{{ queueStatus.consumerCount }}</td> <td>{{ queueStatus.consumerCount }}</td>
<td> <td>
<button class="btn btn-danger btn-mini purge-queue" data-queue-name="{{ queueStatus.queueName }}">{{ 'admin::workermanager:tab:queueMonitor: Purge Queue' | trans }}</button> <button class="btn btn-danger btn-mini purge-queue" data-queue-name="{{ queueStatus.queueName }}">{{ 'admin::workermanager:tab:queueMonitor: Purge Queue' | trans }}</button>
</td> </td>
<td>
<button class="btn btn-danger btn-mini delete-queue" data-queue-name="{{ queueStatus.queueName }}">{{ 'admin::workermanager:tab:queueMonitor: Delete Queue' | trans }}</button>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
@@ -60,5 +64,21 @@
} }
}); });
$("#worker-queue-monitor").on('click', '.delete-queue', function() {
if (confirm("Warning! Are you sure? Messages cannot be recovered after deleting queue.")) {
$.ajax({
type: "POST",
url: "/admin/worker-manager/delete-queue",
dataType: 'json',
data : {
queueName : $(this).attr("data-queue-name")
},
success: function (data) {
$("#refresh-monitor").trigger("click");
}
});
}
});
</script> </script>
{% endif %} {% endif %}

View File

@@ -1,45 +1,41 @@
<h1>{{ 'admin::workermanager:tab:Reminder: description' |trans }}</h1> <h1>{{ 'admin::workermanager:tab:Reminder: description' |trans }}</h1>
<form name="worker_validation_reminder" method="post" action="/admin/worker-manager/validation-reminder"> {{ form_start(form, {'action': path('worker_admin_validationReminder')}) }}
<div class="control-group"> {{ form_widget(form) }}
<div> <br/>
<label for="worker_reminder_interval" class="required">{{ "admin::workermanager:tab:Reminder: Interval in second"|trans }}</label> <br/>
<input type="text" id="worker_reminder_interval" name="worker_reminder_interval" required="required" value="{{ interval }}"> <br/>
</div> <div class="control-group">
</div> {% if(running) %}
<button type="submit" value="stop" class="btn btn-danger stop-validation-reminder">{{ 'Stop' | trans }}</button>
<div class="control-group"> {% else %}
<button class="btn btn-primary start-validation-reminder">{{ "admin::workermanager:tab:Reminder: Start"|trans }}</button> <button type="submit" value="start" class="btn btn-primary start-validation-reminder">{{ "Start"|trans }}</button>
{% endif %}
<button class="btn btn-danger stop-validation-reminder">{{ 'admin::workermanager:tab:Reminder: Stop' | trans }}</button> </div>
</div> {{ form_end(form) }}
</form>
<script type="text/javascript"> <script type="text/javascript">
$("#worker-reminder").on('click', '.stop-validation-reminder', function(e) { $("#worker-reminder").on('click', 'BUTTON[type=submit]', function(e, z) {
e.preventDefault(); e.preventDefault();
if (confirm("Warning! You are about to stop validation Reminder!")) { var button = $(e.target);
$.ajax({ var form = button.closest("FORM");
type: "POST", $("INPUT.act", form).val(button.val()); // "save", "start" or "stop"
url: "/admin/worker-manager/purge-queue",
dataType: 'json',
data : {
queueName : "validationReminder-queue"
},
success: function (data) {
$('#tree li.selected a').trigger('click');
console.log(data);
return false; var ok = true;
{% if(running) %}
if(button.val() === 'save') {
// saving will empty the queue, "stopping"
ok = confirm("Warning! Changing the settings will stop validation Reminder!")
}
{% endif %}
if(button.val() === 'stop') {
// saving will empty the queue, "stopping"
ok = confirm("Warning! You are about to stop validation Reminder!")
}
if(ok) {
form.submit();
} }
}); });
}
});
$("#worker-reminder").on('click', '.start-validation-reminder', function(e) {
e.preventDefault();
$('form[name="worker_validation_reminder"]').submit();
});
</script> </script>

View File

@@ -76,11 +76,6 @@
<tr> <tr>
<td> <td>
<div class="context-menu context-menu-theme-vista"> <div class="context-menu context-menu-theme-vista">
<div title="" class="context-menu-item">
<div class="context-menu-item-inner export-record-action"
data-kind="basket" data-id="{{basket.getId()}}">{{ 'action::exporter' | trans }}
</div>
</div>
{% if app.getAclForUser(app.getAuthenticatedUser()).has_right(constant('\\ACL::CANMODIFRECORD')) %} {% if app.getAclForUser(app.getAuthenticatedUser()).has_right(constant('\\ACL::CANMODIFRECORD')) %}
<div title="" class="context-menu-item menu3-custom-item"> <div title="" class="context-menu-item menu3-custom-item">
<div data-kind="basket" data-id="{{basket.getId()}}" <div data-kind="basket" data-id="{{basket.getId()}}"
@@ -174,15 +169,6 @@
<tr> <tr>
<td> <td>
<div class="context-menu context-menu-theme-flat"> <div class="context-menu context-menu-theme-flat">
<div title="" class="context-menu-item">
<div class="context-menu-item-inner export-record-action"
data-kind="basket" data-id="{{basket.getId()}}">
<a href="#">
<img src="/assets/common/images/icons/export-shadow.png" style="width: 22px;" alt="{{ 'action::exporter' | trans }}">
<span>{{ 'action::exporter' | trans }}</span>
</a>
</div>
</div>
<div title="" class="context-menu-item"> <div title="" class="context-menu-item">
<div class="context-menu-item-inner"> <div class="context-menu-item-inner">
<a href="{{ path('lightbox_compare', { 'basket' : basket.getId() }) }}" target="_blank"> <a href="{{ path('lightbox_compare', { 'basket' : basket.getId() }) }}" target="_blank">
@@ -279,15 +265,6 @@
<tr> <tr>
<td> <td>
<div class="context-menu context-menu-theme-flat"> <div class="context-menu context-menu-theme-flat">
<div title="" class="context-menu-item">
<div class="context-menu-item-inner export-record-action"
data-kind="record" data-id="{{story.getRecord(app).get_serialize_key()}}">
<a href="#">
<img src="/assets/common/images/icons/export-shadow.png" style="width: 22px;" alt="{{ 'action::exporter' | trans }}">
<span>{{ 'action::exporter' | trans }}</span>
</a>
</div>
</div>
{% if app.getAclForUser(app.getAuthenticatedUser()).has_right(constant('\\ACL::CANMODIFRECORD')) %} {% if app.getAclForUser(app.getAuthenticatedUser()).has_right(constant('\\ACL::CANMODIFRECORD')) %}
<div title="" class="context-menu-item menu3-custom-item"> <div title="" class="context-menu-item menu3-custom-item">
<div class="context-menu-item-inner edit-record-action" <div class="context-menu-item-inner edit-record-action"

View File

@@ -9,7 +9,7 @@
{% if record.is_from_basket() %} {% if record.is_from_basket() %}
<a sbas="{{record.get_sbas_id()}}" id="PREV_BASKDEL_{{record.get_serialize_key}}" <a sbas="{{record.get_sbas_id()}}" id="PREV_BASKDEL_{{record.get_serialize_key}}"
class="WorkZoneElementRemover record-remove-from-basket-action" class="WorkZoneElementRemover record-remove-from-basket-action"
data-context="reg_train_basket" data-context="reg_train_basket" style="display: contents;height: 24px;width: 24px;position: relative;bottom: 0px;"
href="{{ path('prod_baskets_basket_element_remove', { 'basket' : record.get_container().getId(), 'basket_element_id' : record.get_original_item().getId()}) }}"> href="{{ path('prod_baskets_basket_element_remove', { 'basket' : record.get_container().getId(), 'basket_element_id' : record.get_original_item().getId()}) }}">
<img src="/assets/common/images/icons/delete.png" height="16" width="16" class="btn-image" title="{{ 'Remove from basket' | trans }}"> <img src="/assets/common/images/icons/delete.png" height="16" width="16" class="btn-image" title="{{ 'Remove from basket' | trans }}">
@@ -17,7 +17,7 @@
{% endif %} {% endif %}
<div class="record-print-action" data-kind="record" data-id="{{record.get_sbas_id()}}_{{record.get_record_id()}}"> <div class="record-print-action" data-kind="record" data-id="{{record.get_sbas_id()}}_{{record.get_record_id()}}">
<img src="/assets/common/images/icons/print_history.png" height="16" width="16" class="btn-image" title="'{{ 'action : print' | trans }}"> <img src="/assets/common/images/icons/print_history.png" height="16" width="16" class="btn-image" title="{{ 'action : print' | trans }}">
</div> </div>
{% if app.getAclForUser(app.getAuthenticatedUser()).has_right_on_base(record.get_base_id(), constant('\\ACL::CANDWNLDHD')) or app.getAclForUser(app.getAuthenticatedUser()).has_right_on_base(record.get_base_id(), constant('\\ACL::CANDWNLDPREVIEW')) %} {% if app.getAclForUser(app.getAuthenticatedUser()).has_right_on_base(record.get_base_id(), constant('\\ACL::CANDWNLDHD')) or app.getAclForUser(app.getAuthenticatedUser()).has_right_on_base(record.get_base_id(), constant('\\ACL::CANDWNLDPREVIEW')) %}