- Fix for #890
- "Channels" feature - Shoutout config param rendered on the homepage - "Clone" feature for campaigns
This commit is contained in:
		
							parent
							
								
									00432e6cfe
								
							
						
					
					
						commit
						d170548cfa
					
				
					 25 changed files with 1009 additions and 525 deletions
				
			
		| 
						 | 
					@ -25,7 +25,7 @@ export default class List extends Component {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                <h2>{t('Mailtrain 2 beta')}</h2>
 | 
					                <h2>{t('Mailtrain 2 beta')}</h2>
 | 
				
			||||||
                <div>{t('Build') + ' 2020-05-28-0102'}</div>
 | 
					                <div>{t('Build') + ' 2020-07-17-0000'}</div>
 | 
				
			||||||
                <p>{this.props.configItems.shoutout}</p>
 | 
					                <p>{this.props.configItems.shoutout}</p>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,7 @@ import {getUrl} from "../lib/urls";
 | 
				
			||||||
import {campaignOverridables, CampaignSource, CampaignStatus, CampaignType} from "../../../shared/campaigns";
 | 
					import {campaignOverridables, CampaignSource, CampaignStatus, CampaignType} from "../../../shared/campaigns";
 | 
				
			||||||
import moment from 'moment';
 | 
					import moment from 'moment';
 | 
				
			||||||
import {getMailerTypes} from "../send-configurations/helpers";
 | 
					import {getMailerTypes} from "../send-configurations/helpers";
 | 
				
			||||||
import {getCampaignLabels} from "./helpers";
 | 
					import {getCampaignLabels, ListsSelectorHelper} from "./helpers";
 | 
				
			||||||
import {withComponentMixins} from "../lib/decorator-helpers";
 | 
					import {withComponentMixins} from "../lib/decorator-helpers";
 | 
				
			||||||
import interoperableErrors from "../../../shared/interoperable-errors";
 | 
					import interoperableErrors from "../../../shared/interoperable-errors";
 | 
				
			||||||
import {Trans} from "react-i18next";
 | 
					import {Trans} from "react-i18next";
 | 
				
			||||||
| 
						 | 
					@ -51,6 +51,8 @@ export default class CUD extends Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const t = props.t;
 | 
					        const t = props.t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.listsSelectorHelper = new ListsSelectorHelper(this, t, 'lists');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN);
 | 
					        this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN);
 | 
				
			||||||
        this.tagLanguages = getTagLanguages(props.t);
 | 
					        this.tagLanguages = getTagLanguages(props.t);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,20 +81,9 @@ export default class CUD extends Component {
 | 
				
			||||||
            [CampaignSource.URL]: t('url')
 | 
					            [CampaignSource.URL]: t('url')
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let sourceLabelsOrder;
 | 
					        const sourceLabelsOrder = [
 | 
				
			||||||
 | 
					            CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN , CampaignSource.TEMPLATE, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.URL
 | 
				
			||||||
        if (props.createFromChannel) {
 | 
					        ];
 | 
				
			||||||
            // If a campaign is created within a channel, we allow only for those source types that makes sense
 | 
					 | 
				
			||||||
            sourceLabelsOrder = [
 | 
					 | 
				
			||||||
                CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN, CampaignSource.CUSTOM_FROM_TEMPLATE
 | 
					 | 
				
			||||||
            ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            // Regular creation or createFromCampaign
 | 
					 | 
				
			||||||
            sourceLabelsOrder = [
 | 
					 | 
				
			||||||
                CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN , CampaignSource.TEMPLATE, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.URL
 | 
					 | 
				
			||||||
            ];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.sourceOptions = [];
 | 
					        this.sourceOptions = [];
 | 
				
			||||||
        for (const key of sourceLabelsOrder) {
 | 
					        for (const key of sourceLabelsOrder) {
 | 
				
			||||||
| 
						 | 
					@ -113,8 +104,6 @@ export default class CUD extends Component {
 | 
				
			||||||
            sendConfiguration: null
 | 
					            sendConfiguration: null
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.nextListEntryId = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.initForm({
 | 
					        this.initForm({
 | 
				
			||||||
            leaveConfirmation: !props.entity || props.entity.permissions.includes('edit'),
 | 
					            leaveConfirmation: !props.entity || props.entity.permissions.includes('edit'),
 | 
				
			||||||
            onChange: {
 | 
					            onChange: {
 | 
				
			||||||
| 
						 | 
					@ -128,17 +117,11 @@ export default class CUD extends Component {
 | 
				
			||||||
        action: PropTypes.string.isRequired,
 | 
					        action: PropTypes.string.isRequired,
 | 
				
			||||||
        entity: PropTypes.object,
 | 
					        entity: PropTypes.object,
 | 
				
			||||||
        createFromChannel: PropTypes.object,
 | 
					        createFromChannel: PropTypes.object,
 | 
				
			||||||
        createFromCampaign: PropTypes.object,
 | 
					        crateFromCampaign: PropTypes.object,
 | 
				
			||||||
        permissions: PropTypes.object,
 | 
					        permissions: PropTypes.object,
 | 
				
			||||||
        type: PropTypes.number
 | 
					        type: PropTypes.number
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getNextListEntryId() {
 | 
					 | 
				
			||||||
        const id = this.nextListEntryId;
 | 
					 | 
				
			||||||
        this.nextListEntryId += 1;
 | 
					 | 
				
			||||||
        return id;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue) {
 | 
					    onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue) {
 | 
				
			||||||
        let match;
 | 
					        let match;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -156,10 +139,7 @@ export default class CUD extends Component {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (key && (match = key.match(/^(lists_[0-9]+_)list$/))) {
 | 
					        this.listsSelectorHelper.onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue);
 | 
				
			||||||
            const prefix = match[1];
 | 
					 | 
				
			||||||
            mutStateData.setIn([prefix + 'segment', 'value'], null);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    onSendConfigurationChanged(newState, key, oldValue, sendConfigurationId) {
 | 
					    onSendConfigurationChanged(newState, key, oldValue, sendConfigurationId) {
 | 
				
			||||||
| 
						 | 
					@ -216,19 +196,7 @@ export default class CUD extends Component {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const lsts = [];
 | 
					        this.listsSelectorHelper.getFormValuesMutator(data);
 | 
				
			||||||
        for (const lst of data.lists) {
 | 
					 | 
				
			||||||
            const lstUid = this.getNextListEntryId();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const prefix = 'lists_' + lstUid + '_';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            data[prefix + 'list'] = lst.list;
 | 
					 | 
				
			||||||
            data[prefix + 'segment'] = lst.segment;
 | 
					 | 
				
			||||||
            data[prefix + 'useSegmentation'] = !!lst.segment;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            lsts.push(lstUid);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        data.lists = lsts;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // noinspection JSIgnoredPromiseFromCall
 | 
					        // noinspection JSIgnoredPromiseFromCall
 | 
				
			||||||
        this.fetchSendConfiguration(data.send_configuration);
 | 
					        this.fetchSendConfiguration(data.send_configuration);
 | 
				
			||||||
| 
						 | 
					@ -275,27 +243,10 @@ export default class CUD extends Component {
 | 
				
			||||||
            delete data[overridable + '_overriden'];
 | 
					            delete data[overridable + '_overriden'];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const lsts = [];
 | 
					        this.listsSelectorHelper.submitFormValuesMutator(data);
 | 
				
			||||||
        for (const lstUid of data.lists) {
 | 
					 | 
				
			||||||
            const prefix = 'lists_' + lstUid + '_';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const useSegmentation = data[prefix + 'useSegmentation'];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            lsts.push({
 | 
					 | 
				
			||||||
                list: data[prefix + 'list'],
 | 
					 | 
				
			||||||
                segment: useSegmentation ? data[prefix + 'segment'] : null
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        data.lists = lsts;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const key in data) {
 | 
					 | 
				
			||||||
            if (key.startsWith('data_') || key.startsWith('lists_')) {
 | 
					 | 
				
			||||||
                delete data[key];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return filterData(data, [
 | 
					        return filterData(data, [
 | 
				
			||||||
            'name', 'description', 'segment', 'namespace', 'send_configuration',
 | 
					            'name', 'description', 'channel', 'namespace', 'send_configuration',
 | 
				
			||||||
            'subject', 'from_name_override', 'from_email_override', 'reply_to_override',
 | 
					            'subject', 'from_name_override', 'from_email_override', 'reply_to_override',
 | 
				
			||||||
            'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url',
 | 
					            'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url',
 | 
				
			||||||
            'type', 'source', 'parent', 'lists'
 | 
					            'type', 'source', 'parent', 'lists'
 | 
				
			||||||
| 
						 | 
					@ -314,10 +265,33 @@ export default class CUD extends Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const data = {};
 | 
					            const data = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
 | 
				
			||||||
 | 
					            data.data_sourceTemplate = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
 | 
				
			||||||
 | 
					            data.data_sourceCampaign = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // This is for CampaignSource.CUSTOM
 | 
				
			||||||
 | 
					            data.data_sourceCustom_type = mailtrainConfig.editors[0];
 | 
				
			||||||
 | 
					            data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
 | 
				
			||||||
 | 
					            data.data_sourceCustom_data = {};
 | 
				
			||||||
 | 
					            data.data_sourceCustom_html = '';
 | 
				
			||||||
 | 
					            data.data_sourceCustom_text = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // This is for CampaignSource.URL
 | 
				
			||||||
 | 
					            data.data_sourceUrl = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // This is for CampaignType.RSS
 | 
				
			||||||
 | 
					            data.data_feedUrl = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (this.props.createFromChannel) {
 | 
					            if (this.props.createFromChannel) {
 | 
				
			||||||
                const channel = this.props.createFromChannel;
 | 
					                const channel = this.props.createFromChannel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                data.channel = channel.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                for (const overridable of campaignOverridables) {
 | 
					                for (const overridable of campaignOverridables) {
 | 
				
			||||||
                    if (channel[overridable + '_override'] === null) {
 | 
					                    if (channel[overridable + '_override'] === null) {
 | 
				
			||||||
                        data[overridable + '_override'] = '';
 | 
					                        data[overridable + '_override'] = '';
 | 
				
			||||||
| 
						 | 
					@ -328,19 +302,7 @@ export default class CUD extends Component {
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const lsts = [];
 | 
					                this.listsSelectorHelper.populateFrom(data, channel.lists);
 | 
				
			||||||
                for (const lst of channel.lists) {
 | 
					 | 
				
			||||||
                    const lstUid = this.getNextListEntryId();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const prefix = 'lists_' + lstUid + '_';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    data[prefix + 'list'] = lst.list;
 | 
					 | 
				
			||||||
                    data[prefix + 'segment'] = lst.segment;
 | 
					 | 
				
			||||||
                    data[prefix + 'useSegmentation'] = !!lst.segment;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    lsts.push(lstUid);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                data.lists = lsts;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                data.type = CampaignType.REGULAR;
 | 
					                data.type = CampaignType.REGULAR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -366,23 +328,27 @@ export default class CUD extends Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (channel.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
					                if (channel.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
				
			||||||
                    data.data_sourceTemplate = channel.sourceTemplate;
 | 
					                    data.data_sourceTemplate = channel.sourceTemplate;
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (channel.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
 | 
					                } else if (channel.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
 | 
				
			||||||
                    channel.data_sourceCampaign = channel.data.sourceCampaign;
 | 
					                    data.data_sourceCampaign = channel.data.sourceCampaign;
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (channel.source === CampaignSource.CUSTOM) {
 | 
					                } else if (channel.source === CampaignSource.CUSTOM) {
 | 
				
			||||||
                    data.data_sourceCustom_type = channel.data.sourceCustom.type;
 | 
					                    data.data_sourceCustom_type = channel.data.sourceCustom.type;
 | 
				
			||||||
                    data.data_sourceCustom_tag_language = channel.data.sourceCustom.tag_language;
 | 
					                    data.data_sourceCustom_tag_language = channel.data.sourceCustom.tag_language;
 | 
				
			||||||
                    data.data_sourceCustom_data = channel.data.sourceCustom.data;
 | 
					                    data.data_sourceCustom_data = channel.data.sourceCustom.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    this.templateTypes[channel.data.sourceCustom.type].afterLoad(data);
 | 
					                    this.templateTypes[channel.data.sourceCustom.type].afterLoad(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                } else if (channel.source === CampaignSource.URL) {
 | 
				
			||||||
 | 
					                    data.data_sourceUrl = channel.data.sourceUrl
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            } else if (this.props.createFromCampaign) {
 | 
					            } else if (this.props.createFromCampaign) {
 | 
				
			||||||
                const sourceCampaign = this.props.createFromCampaign;
 | 
					                const sourceCampaign = this.props.createFromCampaign;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                data.channel = sourceCampaign.channel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                for (const overridable of campaignOverridables) {
 | 
					                for (const overridable of campaignOverridables) {
 | 
				
			||||||
                    if (sourceCampaign[overridable + '_override'] === null) {
 | 
					                    if (sourceCampaign[overridable + '_override'] === null) {
 | 
				
			||||||
                        data[overridable + '_override'] = '';
 | 
					                        data[overridable + '_override'] = '';
 | 
				
			||||||
| 
						 | 
					@ -393,19 +359,7 @@ export default class CUD extends Component {
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const lsts = [];
 | 
					                this.listsSelectorHelper.populateFrom(data, sourceCampaign.lists);
 | 
				
			||||||
                for (const lst of sourceCampaign.lists) {
 | 
					 | 
				
			||||||
                    const lstUid = this.getNextListEntryId();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const prefix = 'lists_' + lstUid + '_';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    data[prefix + 'list'] = lst.list;
 | 
					 | 
				
			||||||
                    data[prefix + 'segment'] = lst.segment;
 | 
					 | 
				
			||||||
                    data[prefix + 'useSegmentation'] = !!lst.segment;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    lsts.push(lstUid);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                data.lists = lsts;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                data.type = sourceCampaign.type;
 | 
					                data.type = sourceCampaign.type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -425,28 +379,6 @@ export default class CUD extends Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                data.unsubscribe_url = sourceCampaign.unsubscribe_url;
 | 
					                data.unsubscribe_url = sourceCampaign.unsubscribe_url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
 | 
					 | 
				
			||||||
                data.data_sourceTemplate = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
 | 
					 | 
				
			||||||
                data.data_sourceCampaign = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is for CampaignSource.CUSTOM
 | 
					 | 
				
			||||||
                data.data_sourceCustom_type = mailtrainConfig.editors[0];
 | 
					 | 
				
			||||||
                data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
 | 
					 | 
				
			||||||
                data.data_sourceCustom_data = {};
 | 
					 | 
				
			||||||
                data.data_sourceCustom_html = '';
 | 
					 | 
				
			||||||
                data.data_sourceCustom_text = '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is for CampaignSource.URL
 | 
					 | 
				
			||||||
                data.data_sourceUrl = '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is for CampaignType.RSS
 | 
					 | 
				
			||||||
                data.data_feedUrl = '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (sourceCampaign.source === CampaignSource.CUSTOM_FROM_TEMPLATE || sourceCampaign.source === CampaignSource.CUSTOM_FROM_CAMPAIGN || sourceCampaign.source === CampaignSource.CUSTOM) {
 | 
					                if (sourceCampaign.source === CampaignSource.CUSTOM_FROM_TEMPLATE || sourceCampaign.source === CampaignSource.CUSTOM_FROM_CAMPAIGN || sourceCampaign.source === CampaignSource.CUSTOM) {
 | 
				
			||||||
                    data.source = CampaignSource.CUSTOM_FROM_CAMPAIGN;
 | 
					                    data.source = CampaignSource.CUSTOM_FROM_CAMPAIGN;
 | 
				
			||||||
                    data.data_sourceCampaign = sourceCampaign.id;
 | 
					                    data.data_sourceCampaign = sourceCampaign.id;
 | 
				
			||||||
| 
						 | 
					@ -466,18 +398,14 @@ export default class CUD extends Component {
 | 
				
			||||||
                    data[overridable + '_overriden'] = false;
 | 
					                    data[overridable + '_overriden'] = false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                data.channel = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                data.type = this.props.type;
 | 
					                data.type = this.props.type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                data.name = '';
 | 
					                data.name = '';
 | 
				
			||||||
                data.description = '';
 | 
					                data.description = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const lstUid = this.getNextListEntryId();
 | 
					                this.listsSelectorHelper.populateFrom(data, [{list: null, segment: null}]);
 | 
				
			||||||
                const lstPrefix = 'lists_' + lstUid + '_';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                data[lstPrefix + 'list'] = null;
 | 
					 | 
				
			||||||
                data[lstPrefix + 'segment'] = null;
 | 
					 | 
				
			||||||
                data[lstPrefix + 'useSegmentation'] = false;
 | 
					 | 
				
			||||||
                data.lists = [lstUid];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                data.send_configuration = null;
 | 
					                data.send_configuration = null;
 | 
				
			||||||
                data.namespace = getDefaultNamespace(this.props.permissions);
 | 
					                data.namespace = getDefaultNamespace(this.props.permissions);
 | 
				
			||||||
| 
						 | 
					@ -490,27 +418,6 @@ export default class CUD extends Component {
 | 
				
			||||||
                data.unsubscribe_url = '';
 | 
					                data.unsubscribe_url = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                data.source = CampaignSource.CUSTOM;
 | 
					                data.source = CampaignSource.CUSTOM;
 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
 | 
					 | 
				
			||||||
                data.data_sourceTemplate = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
 | 
					 | 
				
			||||||
                data.data_sourceCampaign = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is for CampaignSource.CUSTOM
 | 
					 | 
				
			||||||
                data.data_sourceCustom_type = mailtrainConfig.editors[0];
 | 
					 | 
				
			||||||
                data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
 | 
					 | 
				
			||||||
                data.data_sourceCustom_data = {};
 | 
					 | 
				
			||||||
                data.data_sourceCustom_html = '';
 | 
					 | 
				
			||||||
                data.data_sourceCustom_text = '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is for CampaignSource.URL
 | 
					 | 
				
			||||||
                data.data_sourceUrl = '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This is for CampaignType.RSS
 | 
					 | 
				
			||||||
                data.data_feedUrl = '';
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.populateFormValues(data);
 | 
					            this.populateFormValues(data);
 | 
				
			||||||
| 
						 | 
					@ -583,17 +490,7 @@ export default class CUD extends Component {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const lstUid of state.getIn(['lists', 'value'])) {
 | 
					        this.listsSelectorHelper.localValidateFormValues(state)
 | 
				
			||||||
            const prefix = 'lists_' + lstUid + '_';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!state.getIn([prefix + 'list', 'value'])) {
 | 
					 | 
				
			||||||
                state.setIn([prefix + 'list', 'error'], t('listMustBeSelected'));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (state.getIn([prefix + 'useSegmentation', 'value']) && !state.getIn([prefix + 'segment', 'value'])) {
 | 
					 | 
				
			||||||
                state.setIn([prefix + 'segment', 'error'], t('segmentMustBeSelected'));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        validateNamespace(t, state);
 | 
					        validateNamespace(t, state);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -627,7 +524,12 @@ export default class CUD extends Component {
 | 
				
			||||||
                if (afterSubmitAction === CUD.AfterSubmitAction.STATUS) {
 | 
					                if (afterSubmitAction === CUD.AfterSubmitAction.STATUS) {
 | 
				
			||||||
                    this.navigateToWithFlashMessage(`/campaigns/${this.props.entity.id}/status`, 'success', t('campaignUpdated'));
 | 
					                    this.navigateToWithFlashMessage(`/campaigns/${this.props.entity.id}/status`, 'success', t('campaignUpdated'));
 | 
				
			||||||
                } else if (afterSubmitAction === CUD.AfterSubmitAction.LEAVE) {
 | 
					                } else if (afterSubmitAction === CUD.AfterSubmitAction.LEAVE) {
 | 
				
			||||||
                    this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
 | 
					                    const channelId = this.getFormValue('channel');
 | 
				
			||||||
 | 
					                    if (channelId) {
 | 
				
			||||||
 | 
					                        this.navigateToWithFlashMessage(`/channels/${channelId}/campaigns`, 'success', t('campaignUpdated'));
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    await this.getFormValuesFromURL(`rest/campaigns-settings/${this.props.entity.id}`);
 | 
					                    await this.getFormValuesFromURL(`rest/campaigns-settings/${this.props.entity.id}`);
 | 
				
			||||||
                    this.enableForm();
 | 
					                    this.enableForm();
 | 
				
			||||||
| 
						 | 
					@ -642,7 +544,12 @@ export default class CUD extends Component {
 | 
				
			||||||
                    if (afterSubmitAction === CUD.AfterSubmitAction.STATUS) {
 | 
					                    if (afterSubmitAction === CUD.AfterSubmitAction.STATUS) {
 | 
				
			||||||
                        this.navigateToWithFlashMessage(`/campaigns/${submitResult}/status`, 'success', t('campaignCreated'));
 | 
					                        this.navigateToWithFlashMessage(`/campaigns/${submitResult}/status`, 'success', t('campaignCreated'));
 | 
				
			||||||
                    } else if (afterSubmitAction === CUD.AfterSubmitAction.LEAVE) {
 | 
					                    } else if (afterSubmitAction === CUD.AfterSubmitAction.LEAVE) {
 | 
				
			||||||
                        this.navigateToWithFlashMessage(`/campaigns`, 'success', t('campaignCreated'));
 | 
					                        const channelId = this.getFormValue('channel');
 | 
				
			||||||
 | 
					                        if (channelId) {
 | 
				
			||||||
 | 
					                            this.navigateToWithFlashMessage(`/channels/${channelId}/campaigns`, 'success', t('campaignCreated'));
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            this.navigateToWithFlashMessage(`/campaigns`, 'success', t('campaignCreated'));
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        this.navigateToWithFlashMessage(`/campaigns/${submitResult}/edit`, 'success', t('campaignCreated'));
 | 
					                        this.navigateToWithFlashMessage(`/campaigns/${submitResult}/edit`, 'success', t('campaignCreated'));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
| 
						 | 
					@ -654,46 +561,6 @@ export default class CUD extends Component {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    onAddListEntry(orderBeforeIdx) {
 | 
					 | 
				
			||||||
        this.updateForm(mutState => {
 | 
					 | 
				
			||||||
            const lsts = mutState.getIn(['lists', 'value']);
 | 
					 | 
				
			||||||
            let paramId = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const lstUid = this.getNextListEntryId();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const prefix = 'lists_' + lstUid + '_';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mutState.setIn([prefix + 'list', 'value'], null);
 | 
					 | 
				
			||||||
            mutState.setIn([prefix + 'segment', 'value'], null);
 | 
					 | 
				
			||||||
            mutState.setIn([prefix + 'useSegmentation', 'value'], false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mutState.setIn(['lists', 'value'], [...lsts.slice(0, orderBeforeIdx), lstUid, ...lsts.slice(orderBeforeIdx)]);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onRemoveListEntry(lstUid) {
 | 
					 | 
				
			||||||
        this.updateForm(mutState => {
 | 
					 | 
				
			||||||
            const lsts = this.getFormValue('lists');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const prefix = 'lists_' + lstUid + '_';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mutState.delete(prefix + 'list');
 | 
					 | 
				
			||||||
            mutState.delete(prefix + 'segment');
 | 
					 | 
				
			||||||
            mutState.delete(prefix + 'useSegmentation');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mutState.setIn(['lists', 'value'], lsts.filter(val => val !== lstUid));
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onListEntryMoveUp(orderIdx) {
 | 
					 | 
				
			||||||
        const lsts = this.getFormValue('lists');
 | 
					 | 
				
			||||||
        this.updateFormValue('lists', [...lsts.slice(0, orderIdx - 1), lsts[orderIdx], lsts[orderIdx - 1], ...lsts.slice(orderIdx + 1)]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onListEntryMoveDown(orderIdx) {
 | 
					 | 
				
			||||||
        const lsts = this.getFormValue('lists');
 | 
					 | 
				
			||||||
        this.updateFormValue('lists', [...lsts.slice(0, orderIdx), lsts[orderIdx + 1], lsts[orderIdx], ...lsts.slice(orderIdx + 2)]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render() {
 | 
					    render() {
 | 
				
			||||||
        const t = this.props.t;
 | 
					        const t = this.props.t;
 | 
				
			||||||
| 
						 | 
					@ -710,90 +577,13 @@ export default class CUD extends Component {
 | 
				
			||||||
            extraSettings = <InputField id="data_feedUrl" label={t('rssFeedUrl')}/>
 | 
					            extraSettings = <InputField id="data_feedUrl" label={t('rssFeedUrl')}/>
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const listsColumns = [
 | 
					        const channelsColumns = [
 | 
				
			||||||
            { data: 1, title: t('name') },
 | 
					            { data: 1, title: t('name') },
 | 
				
			||||||
            { data: 2, title: t('id'), render: data => <code>{data}</code> },
 | 
					            { data: 2, title: t('id'), render: data => <code>{data}</code> },
 | 
				
			||||||
            { data: 3, title: t('subscribers') },
 | 
					            { data: 3, title: t('description') },
 | 
				
			||||||
            { data: 4, title: t('description') },
 | 
					            { data: 4, title: t('namespace') }
 | 
				
			||||||
            { data: 5, title: t('namespace') }
 | 
					 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const segmentsColumns = [
 | 
					 | 
				
			||||||
            { data: 1, title: t('name') }
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const lstsEditEntries = [];
 | 
					 | 
				
			||||||
        const lsts = this.getFormValue('lists') || [];
 | 
					 | 
				
			||||||
        let lstOrderIdx = 0;
 | 
					 | 
				
			||||||
        for (const lstUid of lsts) {
 | 
					 | 
				
			||||||
            const prefix = 'lists_' + lstUid + '_';
 | 
					 | 
				
			||||||
            const lstOrderIdxClosure = lstOrderIdx;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const selectedList = this.getFormValue(prefix + 'list');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            lstsEditEntries.push(
 | 
					 | 
				
			||||||
                <div key={lstUid} className={campaignsStyles.entry + ' ' + campaignsStyles.entryWithButtons}>
 | 
					 | 
				
			||||||
                    <div className={campaignsStyles.entryButtons}>
 | 
					 | 
				
			||||||
                        {lsts.length > 1 &&
 | 
					 | 
				
			||||||
                        <Button
 | 
					 | 
				
			||||||
                            className="btn-secondary"
 | 
					 | 
				
			||||||
                            icon="trash-alt"
 | 
					 | 
				
			||||||
                            title={t('remove')}
 | 
					 | 
				
			||||||
                            onClickAsync={() => this.onRemoveListEntry(lstUid)}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        <Button
 | 
					 | 
				
			||||||
                            className="btn-secondary"
 | 
					 | 
				
			||||||
                            icon="plus"
 | 
					 | 
				
			||||||
                            title={t('insertNewEntryBeforeThisOne')}
 | 
					 | 
				
			||||||
                            onClickAsync={() => this.onAddListEntry(lstOrderIdxClosure)}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                        {lstOrderIdx > 0 &&
 | 
					 | 
				
			||||||
                        <Button
 | 
					 | 
				
			||||||
                            className="btn-secondary"
 | 
					 | 
				
			||||||
                            icon="chevron-up"
 | 
					 | 
				
			||||||
                            title={t('moveUp')}
 | 
					 | 
				
			||||||
                            onClickAsync={() => this.onListEntryMoveUp(lstOrderIdxClosure)}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        {lstOrderIdx < lsts.length - 1 &&
 | 
					 | 
				
			||||||
                        <Button
 | 
					 | 
				
			||||||
                            className="btn-secondary"
 | 
					 | 
				
			||||||
                            icon="chevron-down"
 | 
					 | 
				
			||||||
                            title={t('moveDown')}
 | 
					 | 
				
			||||||
                            onClickAsync={() => this.onListEntryMoveDown(lstOrderIdxClosure)}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <div className={campaignsStyles.entryContent}>
 | 
					 | 
				
			||||||
                        <TableSelect id={prefix + 'list'} label={t('list')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} />
 | 
					 | 
				
			||||||
                        <div>
 | 
					 | 
				
			||||||
                            <CheckBox id={prefix + 'useSegmentation'} label={t('segment')} text={t('useAParticularSegment')}/>
 | 
					 | 
				
			||||||
                            {selectedList && this.getFormValue(prefix + 'useSegmentation') &&
 | 
					 | 
				
			||||||
                                <TableSelect id={prefix + 'segment'} withHeader dropdown dataUrl={`rest/segments-table/${selectedList}`} columns={segmentsColumns} selectionLabelIndex={1} />
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            lstOrderIdx += 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const lstsEdit =
 | 
					 | 
				
			||||||
            <Fieldset label={t('lists')}>
 | 
					 | 
				
			||||||
                {lstsEditEntries}
 | 
					 | 
				
			||||||
                <div key="newEntry" className={campaignsStyles.newEntry}>
 | 
					 | 
				
			||||||
                    <Button
 | 
					 | 
				
			||||||
                        className="btn-secondary"
 | 
					 | 
				
			||||||
                        icon="plus"
 | 
					 | 
				
			||||||
                        label={t('addList')}
 | 
					 | 
				
			||||||
                        onClickAsync={() => this.onAddListEntry(lsts.length)}
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
            </Fieldset>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const sendConfigurationsColumns = [
 | 
					        const sendConfigurationsColumns = [
 | 
				
			||||||
            { data: 1, title: t('name') },
 | 
					            { data: 1, title: t('name') },
 | 
				
			||||||
            { data: 2, title: t('id'), render: data => <code>{data}</code> },
 | 
					            { data: 2, title: t('id'), render: data => <code>{data}</code> },
 | 
				
			||||||
| 
						 | 
					@ -938,13 +728,15 @@ export default class CUD extends Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <TextArea id="description" label={t('description')}/>
 | 
					                    <TextArea id="description" label={t('description')}/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <TableSelect id="channel" label={t('Channel')} withHeader withClear dropdown dataUrl='rest/channels-with-create-campaign-permission-table' columns={channelsColumns} selectionLabelIndex={1} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    {extraSettings}
 | 
					                    {extraSettings}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <NamespaceSelect/>
 | 
					                    <NamespaceSelect/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <hr/>
 | 
					                    <hr/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    {lstsEdit}
 | 
					                    {this.listsSelectorHelper.render()}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <hr/>
 | 
					                    <hr/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -171,7 +171,12 @@ export default class CustomContent extends Component {
 | 
				
			||||||
            if (afterSubmitAction === CustomContent.AfterSubmitAction.STATUS) {
 | 
					            if (afterSubmitAction === CustomContent.AfterSubmitAction.STATUS) {
 | 
				
			||||||
                this.navigateToWithFlashMessage(`/campaigns/${this.props.entity.id}/status`, 'success', t('campaignUpdated'));
 | 
					                this.navigateToWithFlashMessage(`/campaigns/${this.props.entity.id}/status`, 'success', t('campaignUpdated'));
 | 
				
			||||||
            } else if (afterSubmitAction === CustomContent.AfterSubmitAction.LEAVE) {
 | 
					            } else if (afterSubmitAction === CustomContent.AfterSubmitAction.LEAVE) {
 | 
				
			||||||
                this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
 | 
					                const channelId = this.props.entity.channel;
 | 
				
			||||||
 | 
					                if (channelId) {
 | 
				
			||||||
 | 
					                    this.navigateToWithFlashMessage(`/channels/${channelId}/campaigns`, 'success', t('campaignUpdated'));
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                await this.getFormValuesFromURL(`rest/campaigns-content/${this.props.entity.id}`);
 | 
					                await this.getFormValuesFromURL(`rest/campaigns-content/${this.props.entity.id}`);
 | 
				
			||||||
                this.enableForm();
 | 
					                this.enableForm();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,7 @@ export default class List extends Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const channel = this.props.channel;
 | 
					        const channel = this.props.channel;
 | 
				
			||||||
        const permissions = this.props.permissions;
 | 
					        const permissions = this.props.permissions;
 | 
				
			||||||
        const createPermitted = permissions.createCampaign;
 | 
					        const createPermitted = permissions.createCampaign && (!channel || channel.permissions.includes('createCampaign'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const columns = [];
 | 
					        const columns = [];
 | 
				
			||||||
        columns.push({
 | 
					        columns.push({
 | 
				
			||||||
| 
						 | 
					@ -184,7 +184,11 @@ export default class List extends Component {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <Title>{t('campaigns')}</Title>
 | 
					                <Title>{t('campaigns')}</Title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <Table ref={node => this.table = node} withHeader dataUrl="rest/campaigns-table" columns={columns} order={[6, 'desc']} />
 | 
					                {channel ?
 | 
				
			||||||
 | 
					                    <Table ref={node => this.table = node} withHeader dataUrl={`rest/campaigns-by-channel-table/${channel.id}`} columns={columns} order={[5, 'desc']} />
 | 
				
			||||||
 | 
					                :
 | 
				
			||||||
 | 
					                    <Table ref={node => this.table = node} withHeader dataUrl="rest/campaigns-table" columns={columns} order={[6, 'desc']} />
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,10 @@
 | 
				
			||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {CampaignStatus, CampaignType} from "../../../shared/campaigns";
 | 
					import {CampaignStatus, CampaignType} from "../../../shared/campaigns";
 | 
				
			||||||
 | 
					import campaignsStyles from "./styles.scss";
 | 
				
			||||||
 | 
					import {Button} from "../lib/bootstrap-components";
 | 
				
			||||||
 | 
					import {CheckBox, Fieldset, TableSelect} from "../lib/form";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getCampaignLabels(t) {
 | 
					export function getCampaignLabels(t) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,3 +34,251 @@ export function getCampaignLabels(t) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ListsSelectorHelper {
 | 
				
			||||||
 | 
					    constructor(owner, t, id, allowEmpty = false) {
 | 
				
			||||||
 | 
					        this.owner = owner;
 | 
				
			||||||
 | 
					        this.t = t;
 | 
				
			||||||
 | 
					        this.id = id;
 | 
				
			||||||
 | 
					        this.nextEntryId = 0;
 | 
				
			||||||
 | 
					        this.allowEmpty = allowEmpty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.keyRegex = new RegExp(`^(${id}_[0-9]+_)list$`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getNextEntryId() {
 | 
				
			||||||
 | 
					        const id = this.nextEntryId;
 | 
				
			||||||
 | 
					        this.nextEntryId += 1;
 | 
				
			||||||
 | 
					        return id;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getPrefix(lstUid) {
 | 
				
			||||||
 | 
					        return this.id + '_' + lstUid + '_';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onAddListEntry(orderBeforeIdx) {
 | 
				
			||||||
 | 
					        const owner = this.owner;
 | 
				
			||||||
 | 
					        const id = this.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        owner.updateForm(mutState => {
 | 
				
			||||||
 | 
					            const lsts = mutState.getIn([id, 'value']);
 | 
				
			||||||
 | 
					            let paramId = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const lstUid = this.getNextEntryId();
 | 
				
			||||||
 | 
					            const prefix = this.getPrefix(lstUid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mutState.setIn([prefix + 'list', 'value'], null);
 | 
				
			||||||
 | 
					            mutState.setIn([prefix + 'segment', 'value'], null);
 | 
				
			||||||
 | 
					            mutState.setIn([prefix + 'useSegmentation', 'value'], false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mutState.setIn([id, 'value'], [...lsts.slice(0, orderBeforeIdx), lstUid, ...lsts.slice(orderBeforeIdx)]);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onRemoveListEntry(lstUid) {
 | 
				
			||||||
 | 
					        const owner = this.owner;
 | 
				
			||||||
 | 
					        const id = this.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        owner.updateForm(mutState => {
 | 
				
			||||||
 | 
					            const lsts = owner.getFormValue(id);
 | 
				
			||||||
 | 
					            const prefix = this.getPrefix(lstUid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mutState.delete(prefix + 'list');
 | 
				
			||||||
 | 
					            mutState.delete(prefix + 'segment');
 | 
				
			||||||
 | 
					            mutState.delete(prefix + 'useSegmentation');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mutState.setIn([id, 'value'], lsts.filter(val => val !== lstUid));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onListEntryMoveUp(orderIdx) {
 | 
				
			||||||
 | 
					        const owner = this.owner;
 | 
				
			||||||
 | 
					        const id = this.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const lsts = owner.getFormValue(id);
 | 
				
			||||||
 | 
					        owner.updateFormValue(id, [...lsts.slice(0, orderIdx - 1), lsts[orderIdx], lsts[orderIdx - 1], ...lsts.slice(orderIdx + 1)]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onListEntryMoveDown(orderIdx) {
 | 
				
			||||||
 | 
					        const owner = this.owner;
 | 
				
			||||||
 | 
					        const id = this.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const lsts = owner.getFormValue(id);
 | 
				
			||||||
 | 
					        owner.updateFormValue(id, [...lsts.slice(0, orderIdx), lsts[orderIdx + 1], lsts[orderIdx], ...lsts.slice(orderIdx + 2)]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Public methods
 | 
				
			||||||
 | 
					    onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue) {
 | 
				
			||||||
 | 
					        let match;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (key && (match = key.match(this.keyRegex))) {
 | 
				
			||||||
 | 
					            const prefix = match[1];
 | 
				
			||||||
 | 
					            mutStateData.setIn([prefix + 'segment', 'value'], null);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getFormValuesMutator(data) {
 | 
				
			||||||
 | 
					        const id = this.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const lsts = [];
 | 
				
			||||||
 | 
					        for (const lst of data[id]) {
 | 
				
			||||||
 | 
					            const lstUid = this.getNextEntryId();
 | 
				
			||||||
 | 
					            const prefix = this.getPrefix(lstUid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data[prefix + 'list'] = lst.list;
 | 
				
			||||||
 | 
					            data[prefix + 'segment'] = lst.segment;
 | 
				
			||||||
 | 
					            data[prefix + 'useSegmentation'] = !!lst.segment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lsts.push(lstUid);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        data[id] = lsts;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submitFormValuesMutator(data) {
 | 
				
			||||||
 | 
					        const id = this.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const lsts = [];
 | 
				
			||||||
 | 
					        for (const lstUid of data[id]) {
 | 
				
			||||||
 | 
					            const prefix = this.getPrefix(lstUid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const useSegmentation = data[prefix + 'useSegmentation'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lsts.push({
 | 
				
			||||||
 | 
					                list: data[prefix + 'list'],
 | 
				
			||||||
 | 
					                segment: useSegmentation ? data[prefix + 'segment'] : null
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        data[id] = lsts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const key in data) {
 | 
				
			||||||
 | 
					            if (key.startsWith('data_') || key.startsWith(id + '_')) {
 | 
				
			||||||
 | 
					                delete data[key];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    populateFrom(data, lists) {
 | 
				
			||||||
 | 
					        const id = this.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const lsts = [];
 | 
				
			||||||
 | 
					        for (const lst of lists) {
 | 
				
			||||||
 | 
					            const lstUid = this.getNextEntryId();
 | 
				
			||||||
 | 
					            const prefix = this.getPrefix(lstUid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data[prefix + 'list'] = lst.list;
 | 
				
			||||||
 | 
					            data[prefix + 'segment'] = lst.segment;
 | 
				
			||||||
 | 
					            data[prefix + 'useSegmentation'] = !!lst.segment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lsts.push(lstUid);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        data[id] = lsts;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    localValidateFormValues(state) {
 | 
				
			||||||
 | 
					        const id = this.id;
 | 
				
			||||||
 | 
					        const t = this.t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const lstUid of state.getIn([id, 'value'])) {
 | 
				
			||||||
 | 
					            const prefix = this.getPrefix(lstUid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!state.getIn([prefix + 'list', 'value'])) {
 | 
				
			||||||
 | 
					                state.setIn([prefix + 'list', 'error'], t('listMustBeSelected'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (state.getIn([prefix + 'useSegmentation', 'value']) && !state.getIn([prefix + 'segment', 'value'])) {
 | 
				
			||||||
 | 
					                state.setIn([prefix + 'segment', 'error'], t('segmentMustBeSelected'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    render() {
 | 
				
			||||||
 | 
					        const t = this.t;
 | 
				
			||||||
 | 
					        const owner = this.owner;
 | 
				
			||||||
 | 
					        const id = this.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const listsColumns = [
 | 
				
			||||||
 | 
					            { data: 1, title: t('name') },
 | 
				
			||||||
 | 
					            { data: 2, title: t('id'), render: data => <code>{data}</code> },
 | 
				
			||||||
 | 
					            { data: 3, title: t('subscribers') },
 | 
				
			||||||
 | 
					            { data: 4, title: t('description') },
 | 
				
			||||||
 | 
					            { data: 5, title: t('namespace') }
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const segmentsColumns = [
 | 
				
			||||||
 | 
					            { data: 1, title: t('name') }
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const lstsEditEntries = [];
 | 
				
			||||||
 | 
					        const lsts = owner.getFormValue(id) || [];
 | 
				
			||||||
 | 
					        let lstOrderIdx = 0;
 | 
				
			||||||
 | 
					        for (const lstUid of lsts) {
 | 
				
			||||||
 | 
					            const prefix = this.getPrefix(lstUid);
 | 
				
			||||||
 | 
					            const lstOrderIdxClosure = lstOrderIdx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const selectedList = owner.getFormValue(prefix + 'list');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lstsEditEntries.push(
 | 
				
			||||||
 | 
					                <div key={lstUid} className={campaignsStyles.entry + ' ' + campaignsStyles.entryWithButtons}>
 | 
				
			||||||
 | 
					                    <div className={campaignsStyles.entryButtons}>
 | 
				
			||||||
 | 
					                        {(this.allowEmpty || lsts.length > 1) &&
 | 
				
			||||||
 | 
					                        <Button
 | 
				
			||||||
 | 
					                            className="btn-secondary"
 | 
				
			||||||
 | 
					                            icon="trash-alt"
 | 
				
			||||||
 | 
					                            title={t('remove')}
 | 
				
			||||||
 | 
					                            onClickAsync={() => this.onRemoveListEntry(lstUid)}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        <Button
 | 
				
			||||||
 | 
					                            className="btn-secondary"
 | 
				
			||||||
 | 
					                            icon="plus"
 | 
				
			||||||
 | 
					                            title={t('insertNewEntryBeforeThisOne')}
 | 
				
			||||||
 | 
					                            onClickAsync={() => this.onAddListEntry(lstOrderIdxClosure)}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        {lstOrderIdx > 0 &&
 | 
				
			||||||
 | 
					                        <Button
 | 
				
			||||||
 | 
					                            className="btn-secondary"
 | 
				
			||||||
 | 
					                            icon="chevron-up"
 | 
				
			||||||
 | 
					                            title={t('moveUp')}
 | 
				
			||||||
 | 
					                            onClickAsync={() => this.onListEntryMoveUp(lstOrderIdxClosure)}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        {lstOrderIdx < lsts.length - 1 &&
 | 
				
			||||||
 | 
					                        <Button
 | 
				
			||||||
 | 
					                            className="btn-secondary"
 | 
				
			||||||
 | 
					                            icon="chevron-down"
 | 
				
			||||||
 | 
					                            title={t('moveDown')}
 | 
				
			||||||
 | 
					                            onClickAsync={() => this.onListEntryMoveDown(lstOrderIdxClosure)}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div className={campaignsStyles.entryContent}>
 | 
				
			||||||
 | 
					                        <TableSelect id={prefix + 'list'} label={t('list')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} />
 | 
				
			||||||
 | 
					                        <div>
 | 
				
			||||||
 | 
					                            <CheckBox id={prefix + 'useSegmentation'} label={t('segment')} text={t('useAParticularSegment')}/>
 | 
				
			||||||
 | 
					                            {selectedList && owner.getFormValue(prefix + 'useSegmentation') &&
 | 
				
			||||||
 | 
					                            <TableSelect id={prefix + 'segment'} withHeader dropdown dataUrl={`rest/segments-table/${selectedList}`} columns={segmentsColumns} selectionLabelIndex={1} />
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lstOrderIdx += 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <Fieldset label={t('lists')}>
 | 
				
			||||||
 | 
					                {lstsEditEntries}
 | 
				
			||||||
 | 
					                <div key="newEntry" className={campaignsStyles.newEntry}>
 | 
				
			||||||
 | 
					                    <Button
 | 
				
			||||||
 | 
					                        className="btn-secondary"
 | 
				
			||||||
 | 
					                        icon="plus"
 | 
				
			||||||
 | 
					                        label={t('addList')}
 | 
				
			||||||
 | 
					                        onClickAsync={() => this.onAddListEntry(lsts.length)}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </Fieldset>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										585
									
								
								client/src/channels/CUD.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										585
									
								
								client/src/channels/CUD.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,585 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React, {Component} from 'react';
 | 
				
			||||||
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					import {withTranslation} from '../lib/i18n';
 | 
				
			||||||
 | 
					import {LinkButton, requiresAuthenticatedUser, Title, withPageHelpers} from '../lib/page'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    AlignedRow,
 | 
				
			||||||
 | 
					    Button,
 | 
				
			||||||
 | 
					    ButtonRow,
 | 
				
			||||||
 | 
					    CheckBox,
 | 
				
			||||||
 | 
					    Dropdown,
 | 
				
			||||||
 | 
					    Fieldset,
 | 
				
			||||||
 | 
					    filterData,
 | 
				
			||||||
 | 
					    Form,
 | 
				
			||||||
 | 
					    FormSendMethod,
 | 
				
			||||||
 | 
					    InputField,
 | 
				
			||||||
 | 
					    StaticField,
 | 
				
			||||||
 | 
					    TableSelect,
 | 
				
			||||||
 | 
					    TextArea,
 | 
				
			||||||
 | 
					    withForm,
 | 
				
			||||||
 | 
					    withFormErrorHandlers
 | 
				
			||||||
 | 
					} from '../lib/form';
 | 
				
			||||||
 | 
					import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
 | 
				
			||||||
 | 
					import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
 | 
				
			||||||
 | 
					import {DeleteModalDialog} from "../lib/modals";
 | 
				
			||||||
 | 
					import mailtrainConfig from 'mailtrainConfig';
 | 
				
			||||||
 | 
					import {getTagLanguages, getTemplateTypes, getTypeForm, ResourceType} from '../templates/helpers';
 | 
				
			||||||
 | 
					import axios from '../lib/axios';
 | 
				
			||||||
 | 
					import styles from "../lib/styles.scss";
 | 
				
			||||||
 | 
					import campaignsStyles from "./styles.scss";
 | 
				
			||||||
 | 
					import {getUrl} from "../lib/urls";
 | 
				
			||||||
 | 
					import {campaignOverridables, CampaignSource, CampaignStatus} from "../../../shared/campaigns";
 | 
				
			||||||
 | 
					import moment from 'moment';
 | 
				
			||||||
 | 
					import {getMailerTypes} from "../send-configurations/helpers";
 | 
				
			||||||
 | 
					import {getCampaignLabels, ListsSelectorHelper} from "../campaigns/helpers";
 | 
				
			||||||
 | 
					import {withComponentMixins} from "../lib/decorator-helpers";
 | 
				
			||||||
 | 
					import interoperableErrors from "../../../shared/interoperable-errors";
 | 
				
			||||||
 | 
					import {Trans} from "react-i18next";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@withComponentMixins([
 | 
				
			||||||
 | 
					    withTranslation,
 | 
				
			||||||
 | 
					    withForm,
 | 
				
			||||||
 | 
					    withErrorHandling,
 | 
				
			||||||
 | 
					    withPageHelpers,
 | 
				
			||||||
 | 
					    requiresAuthenticatedUser
 | 
				
			||||||
 | 
					])
 | 
				
			||||||
 | 
					export default class CUD extends Component {
 | 
				
			||||||
 | 
					    constructor(props) {
 | 
				
			||||||
 | 
					        super(props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const t = props.t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.listsSelectorHelper = new ListsSelectorHelper(this, t, 'lists', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN, true);
 | 
				
			||||||
 | 
					        this.tagLanguages = getTagLanguages(props.t);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.mailerTypes = getMailerTypes(props.t);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const { campaignTypeLabels } = getCampaignLabels(t);
 | 
				
			||||||
 | 
					        this.campaignTypeLabels = campaignTypeLabels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.sourceLabels = {
 | 
				
			||||||
 | 
					            [CampaignSource.CUSTOM]: t('customContent'),
 | 
				
			||||||
 | 
					            [CampaignSource.CUSTOM_FROM_CAMPAIGN]: t('customContentClonedFromAnotherCampaign'),
 | 
				
			||||||
 | 
					            [CampaignSource.TEMPLATE]: t('template'),
 | 
				
			||||||
 | 
					            [CampaignSource.CUSTOM_FROM_TEMPLATE]: t('customContentClonedFromTemplate'),
 | 
				
			||||||
 | 
					            [CampaignSource.URL]: t('url')
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const sourceLabelsOrder = [
 | 
				
			||||||
 | 
					            CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN , CampaignSource.TEMPLATE, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.URL
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.sourceOptions = [];
 | 
				
			||||||
 | 
					        for (const key of sourceLabelsOrder) {
 | 
				
			||||||
 | 
					            this.sourceOptions.push({key, label: this.sourceLabels[key]});
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.customTemplateTypeOptions = [];
 | 
				
			||||||
 | 
					        for (const key of mailtrainConfig.editors) {
 | 
				
			||||||
 | 
					            this.customTemplateTypeOptions.push({key, label: this.templateTypes[key].typeName});
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.customTemplateTagLanguageOptions = [];
 | 
				
			||||||
 | 
					        for (const key of mailtrainConfig.tagLanguages) {
 | 
				
			||||||
 | 
					            this.customTemplateTagLanguageOptions.push({key, label: this.tagLanguages[key].name});
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.state = {
 | 
				
			||||||
 | 
					            sendConfiguration: null
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.initForm({
 | 
				
			||||||
 | 
					            onChange: {
 | 
				
			||||||
 | 
					                send_configuration: ::this.onSendConfigurationChanged
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            onChangeBeforeValidation: ::this.onFormChangeBeforeValidation
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static propTypes = {
 | 
				
			||||||
 | 
					        action: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					        entity: PropTypes.object,
 | 
				
			||||||
 | 
					        permissions: PropTypes.object,
 | 
				
			||||||
 | 
					        type: PropTypes.number
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue) {
 | 
				
			||||||
 | 
					        let match;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (key === 'data_sourceCustom_type') {
 | 
				
			||||||
 | 
					            if (newValue) {
 | 
				
			||||||
 | 
					                this.templateTypes[newValue].afterTypeChange(mutStateData);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (key === 'data_sourceCustom_tag_language') {
 | 
				
			||||||
 | 
					            if (newValue) {
 | 
				
			||||||
 | 
					                const currentType = this.getFormValue('data_sourceCustom_type');
 | 
				
			||||||
 | 
					                const isEdit = !!this.props.entity;
 | 
				
			||||||
 | 
					                this.templateTypes[currentType].afterTagLanguageChange(mutStateData, isEdit);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.listsSelectorHelper.onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onSendConfigurationChanged(newState, key, oldValue, sendConfigurationId) {
 | 
				
			||||||
 | 
					        newState.sendConfiguration = null;
 | 
				
			||||||
 | 
					        // noinspection JSIgnoredPromiseFromCall
 | 
				
			||||||
 | 
					        this.fetchSendConfiguration(sendConfigurationId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @withAsyncErrorHandler
 | 
				
			||||||
 | 
					    async fetchSendConfiguration(sendConfigurationId) {
 | 
				
			||||||
 | 
					        if (sendConfigurationId) {
 | 
				
			||||||
 | 
					            this.fetchSendConfigurationId = sendConfigurationId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const result = await axios.get(getUrl(`rest/send-configurations-public/${sendConfigurationId}`));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (sendConfigurationId === this.fetchSendConfigurationId) {
 | 
				
			||||||
 | 
					                    this.setState({
 | 
				
			||||||
 | 
					                        sendConfiguration: result.data
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (err) {
 | 
				
			||||||
 | 
					                if (err instanceof interoperableErrors.PermissionDeniedError) {
 | 
				
			||||||
 | 
					                    this.setState({
 | 
				
			||||||
 | 
					                        sendConfiguration: null
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    throw err;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    populateTemplateDefaults(data) {
 | 
				
			||||||
 | 
					        // This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
 | 
				
			||||||
 | 
					        data.data_sourceTemplate = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
 | 
				
			||||||
 | 
					        data.data_sourceCampaign = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // This is for CampaignSource.CUSTOM
 | 
				
			||||||
 | 
					        data.data_sourceCustom_type = mailtrainConfig.editors[0];
 | 
				
			||||||
 | 
					        data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
 | 
				
			||||||
 | 
					        data.data_sourceCustom_data = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // This is for CampaignSource.URL
 | 
				
			||||||
 | 
					        data.data_sourceUrl = '';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getFormValuesMutator(data) {
 | 
				
			||||||
 | 
					        this.populateTemplateDefaults(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (data.source === CampaignSource.TEMPLATE || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
				
			||||||
 | 
					            data.data_sourceTemplate = data.data.sourceTemplate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (data.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
 | 
				
			||||||
 | 
					            data.data_sourceCampaign = data.data.sourceCampaign;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (data.source === CampaignSource.CUSTOM) {
 | 
				
			||||||
 | 
					            data.data_sourceCustom_type = data.data.sourceCustom.type;
 | 
				
			||||||
 | 
					            data.data_sourceCustom_tag_language = data.data.sourceCustom.tag_language;
 | 
				
			||||||
 | 
					            data.data_sourceCustom_data = data.data.sourceCustom.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.templateTypes[data.data.sourceCustom.type].afterLoad(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (data.source === CampaignSource.URL) {
 | 
				
			||||||
 | 
					            data.data_sourceUrl = data.data.sourceUrl
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const overridable of campaignOverridables) {
 | 
				
			||||||
 | 
					            if (data[overridable + '_override'] === null) {
 | 
				
			||||||
 | 
					                data[overridable + '_override'] = '';
 | 
				
			||||||
 | 
					                data[overridable + '_overriden'] = false;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                data[overridable + '_overriden'] = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.listsSelectorHelper.getFormValuesMutator(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // noinspection JSIgnoredPromiseFromCall
 | 
				
			||||||
 | 
					        this.fetchSendConfiguration(data.send_configuration);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submitFormValuesMutator(data) {
 | 
				
			||||||
 | 
					        const isEdit = !!this.props.entity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data.source = Number.parseInt(data.source);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data.data = {};
 | 
				
			||||||
 | 
					        if (data.source === CampaignSource.TEMPLATE || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
				
			||||||
 | 
					            data.data.sourceTemplate = data.data_sourceTemplate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (data.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
 | 
				
			||||||
 | 
					            data.data.sourceCampaign = data.data_sourceCampaign;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (data.source === CampaignSource.CUSTOM) {
 | 
				
			||||||
 | 
					            this.templateTypes[data.data_sourceCustom_type].beforeSave(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data.data.sourceCustom = {
 | 
				
			||||||
 | 
					                type: data.data_sourceCustom_type,
 | 
				
			||||||
 | 
					                tag_language: data.data_sourceCustom_tag_language,
 | 
				
			||||||
 | 
					                data: data.data_sourceCustom_data,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (data.source === CampaignSource.URL) {
 | 
				
			||||||
 | 
					            data.data.sourceUrl = data.data_sourceUrl;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const overridable of campaignOverridables) {
 | 
				
			||||||
 | 
					            if (!data[overridable + '_overriden']) {
 | 
				
			||||||
 | 
					                data[overridable + '_override'] = null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            delete data[overridable + '_overriden'];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.listsSelectorHelper.submitFormValuesMutator(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return filterData(data, [
 | 
				
			||||||
 | 
					            'name', 'description', 'namespace', 'cpg_name', 'cpg_description', 'send_configuration',
 | 
				
			||||||
 | 
					            'subject', 'from_name_override', 'from_email_override', 'reply_to_override',
 | 
				
			||||||
 | 
					            'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url',
 | 
				
			||||||
 | 
					            'source', 'lists'
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    componentDidMount() {
 | 
				
			||||||
 | 
					        if (this.props.entity) {
 | 
				
			||||||
 | 
					            this.getFormValuesFromEntity(this.props.entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const data = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (const overridable of campaignOverridables) {
 | 
				
			||||||
 | 
					                data[overridable + '_override'] = '';
 | 
				
			||||||
 | 
					                data[overridable + '_overriden'] = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data.type = this.props.type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data.name = '';
 | 
				
			||||||
 | 
					            data.description = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data.cpg_name = '';
 | 
				
			||||||
 | 
					            data.cpg_description = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.listsSelectorHelper.populateFrom(data, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data.send_configuration = null;
 | 
				
			||||||
 | 
					            data.namespace = getDefaultNamespace(this.props.permissions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data.subject = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data.click_tracking_disabled = false;
 | 
				
			||||||
 | 
					            data.open_tracking_disabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data.unsubscribe_url = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data.source = CampaignSource.CUSTOM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.populateTemplateDefaults(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.populateFormValues(data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    localValidateFormValues(state) {
 | 
				
			||||||
 | 
					        const t = this.props.t;
 | 
				
			||||||
 | 
					        const isEdit = !!this.props.entity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const key of state.keys()) {
 | 
				
			||||||
 | 
					            state.setIn([key, 'error'], null);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!state.getIn(['name', 'value'])) {
 | 
				
			||||||
 | 
					            state.setIn(['name', 'error'], t('nameMustNotBeEmpty'));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const sourceTypeKey = Number.parseInt(state.getIn(['source', 'value']));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (sourceTypeKey === CampaignSource.TEMPLATE || sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
				
			||||||
 | 
					            if (!state.getIn(['data_sourceTemplate', 'value'])) {
 | 
				
			||||||
 | 
					                state.setIn(['data_sourceTemplate', 'error'], t('templateMustBeSelected'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
 | 
				
			||||||
 | 
					            if (!state.getIn(['data_sourceCampaign', 'value'])) {
 | 
				
			||||||
 | 
					                state.setIn(['data_sourceCampaign', 'error'], t('campaignMustBeSelected'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (sourceTypeKey === CampaignSource.CUSTOM) {
 | 
				
			||||||
 | 
					            const customTemplateTypeKey = state.getIn(['data_sourceCustom_type', 'value']);
 | 
				
			||||||
 | 
					            if (!customTemplateTypeKey) {
 | 
				
			||||||
 | 
					                state.setIn(['data_sourceCustom_type', 'error'], t('typeMustBeSelected'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!state.getIn(['data_sourceCustom_tag_language', 'value'])) {
 | 
				
			||||||
 | 
					                state.setIn(['data_sourceCustom_tag_language', 'error'], t('tagLanguageMustBeSelected'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (customTemplateTypeKey) {
 | 
				
			||||||
 | 
					                this.templateTypes[customTemplateTypeKey].validate(state);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (sourceTypeKey === CampaignSource.URL) {
 | 
				
			||||||
 | 
					            if (!state.getIn(['data_sourceUrl', 'value'])) {
 | 
				
			||||||
 | 
					                state.setIn(['data_sourceUrl', 'error'], t('urlMustNotBeEmpty'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.listsSelectorHelper.localValidateFormValues(state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        validateNamespace(t, state);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async save() {
 | 
				
			||||||
 | 
					        await this.submitHandler();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @withFormErrorHandlers
 | 
				
			||||||
 | 
					    async submitHandler(submitAndLeave) {
 | 
				
			||||||
 | 
					        const t = this.props.t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let sendMethod, url;
 | 
				
			||||||
 | 
					        if (this.props.entity) {
 | 
				
			||||||
 | 
					            sendMethod = FormSendMethod.PUT;
 | 
				
			||||||
 | 
					            url = `rest/channels/${this.props.entity.id}`;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            sendMethod = FormSendMethod.POST;
 | 
				
			||||||
 | 
					            url = 'rest/channels'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.disableForm();
 | 
				
			||||||
 | 
					        this.setFormStatusMessage('info', t('saving'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (submitResult) {
 | 
				
			||||||
 | 
					            if (this.props.entity) {
 | 
				
			||||||
 | 
					                if (submitAndLeave) {
 | 
				
			||||||
 | 
					                    this.navigateToWithFlashMessage('/channels', 'success', t('Channel updated'));
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    await this.getFormValuesFromURL(`rest/channels/${this.props.entity.id}`);
 | 
				
			||||||
 | 
					                    this.enableForm();
 | 
				
			||||||
 | 
					                    this.setFormStatusMessage('success', t('Channel updated'));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (submitAndLeave) {
 | 
				
			||||||
 | 
					                    this.navigateToWithFlashMessage('/channels', 'success', t('Channel created'));
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    this.navigateToWithFlashMessage(`/channels/${submitResult}/edit`, 'success', t('Channel created'));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            this.enableForm();
 | 
				
			||||||
 | 
					            this.setFormStatusMessage('warning', t('thereAreErrorsInTheFormPleaseFixThemAnd'));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    render() {
 | 
				
			||||||
 | 
					        const t = this.props.t;
 | 
				
			||||||
 | 
					        const isEdit = !!this.props.entity;
 | 
				
			||||||
 | 
					        const canModify = !isEdit || this.props.entity.permissions.includes('edit');
 | 
				
			||||||
 | 
					        const canDelete = isEdit && this.props.entity.permissions.includes('delete');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const sourceTypeKey = Number.parseInt(this.getFormValue('source'));
 | 
				
			||||||
 | 
					        const campaignTypeKey = this.getFormValue('type');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const sendConfigurationsColumns = [
 | 
				
			||||||
 | 
					            { data: 1, title: t('name') },
 | 
				
			||||||
 | 
					            { data: 2, title: t('id'), render: data => <code>{data}</code> },
 | 
				
			||||||
 | 
					            { data: 3, title: t('description') },
 | 
				
			||||||
 | 
					            { data: 4, title: t('type'), render: data => this.mailerTypes[data].typeName },
 | 
				
			||||||
 | 
					            { data: 6, title: t('namespace') }
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let sendSettings;
 | 
				
			||||||
 | 
					        if (this.getFormValue('send_configuration')) {
 | 
				
			||||||
 | 
					            if (this.state.sendConfiguration) {
 | 
				
			||||||
 | 
					                sendSettings = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const addOverridable = (id, label) => {
 | 
				
			||||||
 | 
					                    if(this.state.sendConfiguration[id + '_overridable']){
 | 
				
			||||||
 | 
					                        if (this.getFormValue(id + '_overriden')) {
 | 
				
			||||||
 | 
					                            sendSettings.push(<InputField label={label} key={id + '_override'} id={id + '_override'}/>);
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            sendSettings.push(
 | 
				
			||||||
 | 
					                                <StaticField key={id + '_original'} label={label} id={id + '_original'} className={styles.formDisabled}>
 | 
				
			||||||
 | 
					                                    {this.state.sendConfiguration[id]}
 | 
				
			||||||
 | 
					                                </StaticField>
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        sendSettings.push(<CheckBox key={id + '_overriden'} id={id + '_overriden'} text={t('override')} className={campaignsStyles.overrideCheckbox}/>);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else{
 | 
				
			||||||
 | 
					                        sendSettings.push(
 | 
				
			||||||
 | 
					                            <StaticField key={id + '_original'} label={label} id={id + '_original'} className={styles.formDisabled}>
 | 
				
			||||||
 | 
					                                {this.state.sendConfiguration[id]}
 | 
				
			||||||
 | 
					                            </StaticField>
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                addOverridable('from_name', t('fromName'));
 | 
				
			||||||
 | 
					                addOverridable('from_email', t('fromEmailAddress'));
 | 
				
			||||||
 | 
					                addOverridable('reply_to', t('replytoEmailAddress'));
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                sendSettings =  <AlignedRow>{t('loadingSendConfiguration')}</AlignedRow>
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            sendSettings = null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let templateEdit = null;
 | 
				
			||||||
 | 
					        if (sourceTypeKey === CampaignSource.TEMPLATE || (!isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE)) {
 | 
				
			||||||
 | 
					            const templatesColumns = [
 | 
				
			||||||
 | 
					                { data: 1, title: t('name') },
 | 
				
			||||||
 | 
					                { data: 2, title: t('description') },
 | 
				
			||||||
 | 
					                { data: 3, title: t('type'), render: data => this.templateTypes[data].typeName },
 | 
				
			||||||
 | 
					                { data: 5, title: t('created'), render: data => moment(data).fromNow() },
 | 
				
			||||||
 | 
					                { data: 6, title: t('namespace') },
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let help = null;
 | 
				
			||||||
 | 
					            if (sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
				
			||||||
 | 
					                help = t('selectingATemplateCreatesACampaign');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // The "key" property here and in the TableSelect below is to tell React that these tables are different and should be rendered by different instances. Otherwise, React will use
 | 
				
			||||||
 | 
					            // only one instance, which fails because Table does not handle updates in "columns" property
 | 
				
			||||||
 | 
					            templateEdit = <TableSelect key="templateSelect" id="data_sourceTemplate" label={t('template')} withHeader dropdown dataUrl='rest/templates-table' columns={templatesColumns} selectionLabelIndex={1} help={help}/>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
 | 
				
			||||||
 | 
					            const campaignsColumns = [
 | 
				
			||||||
 | 
					                { data: 1, title: t('name') },
 | 
				
			||||||
 | 
					                { data: 2, title: t('id'), render: data => <code>{data}</code> },
 | 
				
			||||||
 | 
					                { data: 3, title: t('description') },
 | 
				
			||||||
 | 
					                { data: 4, title: t('type'), render: data => this.campaignTypeLabels[data] },
 | 
				
			||||||
 | 
					                { data: 5, title: t('created'), render: data => moment(data).fromNow() },
 | 
				
			||||||
 | 
					                { data: 6, title: t('namespace') }
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            templateEdit = <TableSelect key="campaignSelect" id="data_sourceCampaign" label={t('campaign')} withHeader dropdown dataUrl='rest/campaigns-with-content-table' columns={campaignsColumns} selectionLabelIndex={1} help={t('contentOfTheSelectedCampaignWillBeCopied')}/>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (sourceTypeKey === CampaignSource.CUSTOM) {
 | 
				
			||||||
 | 
					            const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let customTemplateTypeForm = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (customTemplateTypeKey) {
 | 
				
			||||||
 | 
					                customTemplateTypeForm = getTypeForm(this, customTemplateTypeKey, false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            templateEdit = <div>
 | 
				
			||||||
 | 
					                <Dropdown id="data_sourceCustom_type" label={t('type')} options={this.customTemplateTypeOptions}/>
 | 
				
			||||||
 | 
					                <Dropdown id="data_sourceCustom_tag_language" label={t('tagLanguage')} options={this.customTemplateTagLanguageOptions}/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                {customTemplateTypeForm}
 | 
				
			||||||
 | 
					            </div>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if (sourceTypeKey === CampaignSource.URL) {
 | 
				
			||||||
 | 
					            templateEdit = <InputField id="data_sourceUrl" label={t('renderUrl')} help={t('ifAMessageIsSentThenThisUrlWillBePosTed')}/>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                {canDelete &&
 | 
				
			||||||
 | 
					                    <DeleteModalDialog
 | 
				
			||||||
 | 
					                        stateOwner={this}
 | 
				
			||||||
 | 
					                        visible={this.props.action === 'delete'}
 | 
				
			||||||
 | 
					                        deleteUrl={`rest/channels/${this.props.entity.id}`}
 | 
				
			||||||
 | 
					                        backUrl={`/channels/${this.props.entity.id}/edit`}
 | 
				
			||||||
 | 
					                        successUrl="/channels"
 | 
				
			||||||
 | 
					                        deletingMsg={t('Deleting channel ...')}
 | 
				
			||||||
 | 
					                        deletedMsg={t('Channel deleted')}/>
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <Title>{isEdit ? t('Edit Channel') : t('Create Channel')}</Title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                {!canModify &&
 | 
				
			||||||
 | 
					                <div className="alert alert-warning" role="alert">
 | 
				
			||||||
 | 
					                    <Trans><b>Warning!</b> You do not have necessary permissions to edit this channel. Any changes that you perform here will be lost.</Trans>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                {isEdit && this.props.entity.status === CampaignStatus.SENDING &&
 | 
				
			||||||
 | 
					                    <div className={`alert alert-info`} role="alert">
 | 
				
			||||||
 | 
					                        {t('formCannotBeEditedBecauseTheCampaignIs')}
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <Form stateOwner={this} onSubmitAsync={::this.submitHandler}>
 | 
				
			||||||
 | 
					                    <InputField id="name" label={t('name')}/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    {isEdit &&
 | 
				
			||||||
 | 
					                    <StaticField id="cid" className={styles.formDisabled} label={t('id')}>
 | 
				
			||||||
 | 
					                        {this.getFormValue('cid')}
 | 
				
			||||||
 | 
					                    </StaticField>
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <TextArea id="description" label={t('description')}/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <NamespaceSelect/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <hr/>
 | 
				
			||||||
 | 
					                    <Fieldset label={t('Campaign defaults')}>
 | 
				
			||||||
 | 
					                        <InputField id="cpg_name" label={t('Campaign name')}/>
 | 
				
			||||||
 | 
					                        <TextArea id="cpg_description" label={t('Campaign description')}/>
 | 
				
			||||||
 | 
					                    </Fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <hr/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    {this.listsSelectorHelper.render()}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <hr/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <Fieldset label={t('sendSettings')}>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <TableSelect id="send_configuration" label={t('sendConfiguration')} withHeader withClear dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        {sendSettings}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <InputField label={t('subjectLine')} key="subject" id="subject"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <InputField id="unsubscribe_url" label={t('customUnsubscribeUrl')}/>
 | 
				
			||||||
 | 
					                    </Fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <hr/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <Fieldset label={t('tracking')}>
 | 
				
			||||||
 | 
					                        <CheckBox id="open_tracking_disabled" text={t('disableOpenedTracking')}/>
 | 
				
			||||||
 | 
					                        <CheckBox id="click_tracking_disabled" text={t('disableClickedTracking')}/>
 | 
				
			||||||
 | 
					                    </Fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <hr/>
 | 
				
			||||||
 | 
					                    <Fieldset label={t('template')}>
 | 
				
			||||||
 | 
					                        <Dropdown id="source" label={t('contentSource')} options={this.sourceOptions}/>
 | 
				
			||||||
 | 
					                    </Fieldset>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    {templateEdit}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <ButtonRow>
 | 
				
			||||||
 | 
					                        {canModify &&
 | 
				
			||||||
 | 
					                            <>
 | 
				
			||||||
 | 
					                                <Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
 | 
				
			||||||
 | 
					                                {isEdit && <Button type="submit" className="btn-primary" icon="check" label={t('saveAndLeave')} onClickAsync={async () => await this.submitHandler(true)}/>}
 | 
				
			||||||
 | 
					                            </>
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        {canDelete && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/channels/${this.props.entity.id}/delete`}/> }
 | 
				
			||||||
 | 
					                    </ButtonRow>
 | 
				
			||||||
 | 
					                </Form>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -42,7 +42,7 @@ export default class List extends Component {
 | 
				
			||||||
                data: 1,
 | 
					                data: 1,
 | 
				
			||||||
                title: t('name'),
 | 
					                title: t('name'),
 | 
				
			||||||
                actions: data => {
 | 
					                actions: data => {
 | 
				
			||||||
                    const perms = data[10];
 | 
					                    const perms = data[5];
 | 
				
			||||||
                    if (perms.includes('view')) {
 | 
					                    if (perms.includes('view')) {
 | 
				
			||||||
                        return [{label: data[1], link: `/channels/${data[0]}/campaigns`}];
 | 
					                        return [{label: data[1], link: `/channels/${data[0]}/campaigns`}];
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ import React from 'react';
 | 
				
			||||||
import CampaignsList from '../campaigns/List';
 | 
					import CampaignsList from '../campaigns/List';
 | 
				
			||||||
import CampaignsCUD from '../campaigns/CUD';
 | 
					import CampaignsCUD from '../campaigns/CUD';
 | 
				
			||||||
import ChannelsList from './List';
 | 
					import ChannelsList from './List';
 | 
				
			||||||
//import ChannelsCUD from './CUD';
 | 
					import ChannelsCUD from './CUD';
 | 
				
			||||||
import Share from '../shares/Share';
 | 
					import Share from '../shares/Share';
 | 
				
			||||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers"
 | 
					import {ellipsizeBreadcrumbLabel} from "../lib/helpers"
 | 
				
			||||||
import {namespaceCheckPermissions} from "../lib/namespace";
 | 
					import {namespaceCheckPermissions} from "../lib/namespace";
 | 
				
			||||||
| 
						 | 
					@ -29,7 +29,7 @@ function getMenus(t) {
 | 
				
			||||||
            panelRender: props => <ChannelsList permissions={props.permissions}/>,
 | 
					            panelRender: props => <ChannelsList permissions={props.permissions}/>,
 | 
				
			||||||
            children: {
 | 
					            children: {
 | 
				
			||||||
                ':channelId([0-9]+)': {
 | 
					                ':channelId([0-9]+)': {
 | 
				
			||||||
                    title: resolved => t('channelName', {name: ellipsizeBreadcrumbLabel(resolved.channel.name)}),
 | 
					                    title: resolved => t('Channel "{{name}}"', {name: ellipsizeBreadcrumbLabel(resolved.channel.name)}),
 | 
				
			||||||
                    resolve: {
 | 
					                    resolve: {
 | 
				
			||||||
                        channel: params => `rest/channels/${params.channelId}`
 | 
					                        channel: params => `rest/channels/${params.channelId}`
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
| 
						 | 
					@ -39,16 +39,14 @@ function getMenus(t) {
 | 
				
			||||||
                            title: t('Campaigns'),
 | 
					                            title: t('Campaigns'),
 | 
				
			||||||
                            link: params => `/channels/${params.channelId}/campaigns`,
 | 
					                            link: params => `/channels/${params.channelId}/campaigns`,
 | 
				
			||||||
                            visible: resolved => resolved.channel.permissions.includes('view'),
 | 
					                            visible: resolved => resolved.channel.permissions.includes('view'),
 | 
				
			||||||
                            panelRender: props => <CampaignsList channel={props.resolved.channel} />
 | 
					                            panelRender: props => <CampaignsList channel={props.resolved.channel} permissions={props.permissions} />
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                        /*
 | 
					 | 
				
			||||||
                        ':action(edit|delete)': {
 | 
					                        ':action(edit|delete)': {
 | 
				
			||||||
                            title: t('edit'),
 | 
					                            title: t('edit'),
 | 
				
			||||||
                            link: params => `/channels/${params.channelId}/edit`,
 | 
					                            link: params => `/channels/${params.channelId}/edit`,
 | 
				
			||||||
                            visible: resolved => resolved.channel.permissions.includes('view') || resolved.channel.permissions.includes('edit'),
 | 
					                            visible: resolved => resolved.channel.permissions.includes('view') || resolved.channel.permissions.includes('edit'),
 | 
				
			||||||
                            panelRender: props => <ChannelsCUD action={props.match.params.action} entity={props.resolved.channel} permissions={props.permissions} />
 | 
					                            panelRender: props => <ChannelsCUD action={props.match.params.action} entity={props.resolved.channel} permissions={props.permissions} />
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                        */
 | 
					 | 
				
			||||||
                        share: {
 | 
					                        share: {
 | 
				
			||||||
                            title: t('share'),
 | 
					                            title: t('share'),
 | 
				
			||||||
                            link: params => `/channels/${params.channelId}/share`,
 | 
					                            link: params => `/channels/${params.channelId}/share`,
 | 
				
			||||||
| 
						 | 
					@ -61,7 +59,7 @@ function getMenus(t) {
 | 
				
			||||||
                            title: t('createCampaign'),
 | 
					                            title: t('createCampaign'),
 | 
				
			||||||
                            link: params => `/channels/${params.channelId}/create`,
 | 
					                            link: params => `/channels/${params.channelId}/create`,
 | 
				
			||||||
                            visible: resolved => resolved.channel.permissions.includes('createCampaign'),
 | 
					                            visible: resolved => resolved.channel.permissions.includes('createCampaign'),
 | 
				
			||||||
                            panelRender: props => <CampaignsCUD action="create" channel={props.resolved.channel} permissions={props.permissions} />
 | 
					                            panelRender: props => <CampaignsCUD action="create" createFromChannel={props.resolved.channel} permissions={props.permissions} />,
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,8 +34,6 @@ import styles from "./styles.scss";
 | 
				
			||||||
import moment from "moment";
 | 
					import moment from "moment";
 | 
				
			||||||
import {getUrl} from "./urls";
 | 
					import {getUrl} from "./urls";
 | 
				
			||||||
import {createComponentMixin, withComponentMixins} from "./decorator-helpers";
 | 
					import {createComponentMixin, withComponentMixins} from "./decorator-helpers";
 | 
				
			||||||
import cudStyles from "../../../mvis/ivis-core/client/src/settings/jobs/CUD.scss";
 | 
					 | 
				
			||||||
import {campaignOverridables, CampaignSource, CampaignType} from "../../../shared/campaigns";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const FormState = {
 | 
					const FormState = {
 | 
				
			||||||
| 
						 | 
					@ -931,207 +929,6 @@ class ButtonRow extends Component {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@withComponentMixins([
 | 
					 | 
				
			||||||
    withTranslation,
 | 
					 | 
				
			||||||
    withFormStateOwner
 | 
					 | 
				
			||||||
], null, ['submitFormValuesMutator', 'getFormValueIdForPicker'])
 | 
					 | 
				
			||||||
class ListCreator extends Component {
 | 
					 | 
				
			||||||
    static propTypes = {
 | 
					 | 
				
			||||||
        id: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
        label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
 | 
					 | 
				
			||||||
        help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
 | 
					 | 
				
			||||||
        className: PropTypes.string,
 | 
					 | 
				
			||||||
        withOrder: PropTypes.bool,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor(props) {
 | 
					 | 
				
			||||||
        super(props);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this._nextEntryId = 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    componentDidMount() {
 | 
					 | 
				
			||||||
        const values = this.props.initValues;
 | 
					 | 
				
			||||||
        if (values && values.length > 0) {
 | 
					 | 
				
			||||||
            this.getFormStateOwner().updateForm(mutState => {
 | 
					 | 
				
			||||||
                let entryIds = mutState.getIn([this.props.id, 'value']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (!entryIds) {
 | 
					 | 
				
			||||||
                    entryIds = [];
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for (const entryValue of values) {
 | 
					 | 
				
			||||||
                    const entryId = this.getNextEntryId();
 | 
					 | 
				
			||||||
                    mutState.setIn([this.getFormValueId(entryId), 'value'], entryValue);
 | 
					 | 
				
			||||||
                    entryIds.push(entryId);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                mutState.setIn([this.props.id, 'value'], entryIds);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static getFormValuesMutator(pickerId, data) {
 | 
					 | 
				
			||||||
        const elems = [];
 | 
					 | 
				
			||||||
        for (const elem of data[pickerId]) {
 | 
					 | 
				
			||||||
            const uid = this.getNextEntryId();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const prefix = this.getFormValueId(uid);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            data[prefix + 'list'] = elem.list;
 | 
					 | 
				
			||||||
            data[prefix + 'segment'] = elem.segment;
 | 
					 | 
				
			||||||
            data[prefix + 'useSegmentation'] = !!elem.segment;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            elems.push(uid);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        data[pickerId] = elems;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static submitFormValuesMutator(pickerId, data) {
 | 
					 | 
				
			||||||
        const entryValues = [];
 | 
					 | 
				
			||||||
        const entryIds = data[pickerId];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!entryIds) {
 | 
					 | 
				
			||||||
            return entryValues;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const entryId of entryIds) {
 | 
					 | 
				
			||||||
            const entryFormId = ListCreator.getFormValueIdForPicker(pickerId, entryId);
 | 
					 | 
				
			||||||
            const value = data[entryFormId];
 | 
					 | 
				
			||||||
            entryValues.push(value);
 | 
					 | 
				
			||||||
            delete data[entryFormId]
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data[pickerId] = entryValues;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static getFormValueIdForPicker(pickerId, entryId) {
 | 
					 | 
				
			||||||
        return `${pickerId}_${entryId}`;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    getFormValueId(entryId) {
 | 
					 | 
				
			||||||
        return ListCreator.getFormValueIdForPicker(this.props.id, entryId);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    getNextEntryId() {
 | 
					 | 
				
			||||||
        return this._nextEntryId++;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onAddListEntry(positionBefore) {
 | 
					 | 
				
			||||||
        this.getFormStateOwner().updateForm(mutState => {
 | 
					 | 
				
			||||||
            let entryIds = mutState.getIn([this.props.id, 'value']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!entryIds) {
 | 
					 | 
				
			||||||
                entryIds = [];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (positionBefore == null) {
 | 
					 | 
				
			||||||
                positionBefore = entryIds.length;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const entryId = this.getNextEntryId();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mutState.setIn([this.getFormValueId(entryId), 'value'], null);
 | 
					 | 
				
			||||||
            mutState.setIn([this.props.id, 'value'], [...entryIds.slice(0, positionBefore), entryId, ...entryIds.slice(positionBefore)]);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onRemoveSetEntry(entryId) {
 | 
					 | 
				
			||||||
        this.getFormStateOwner().updateForm(mutState => {
 | 
					 | 
				
			||||||
            const entryIds = mutState.getIn([this.props.id, 'value']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mutState.delete(this.getFormValueId(entryId));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mutState.setIn([this.props.id, 'value'], entryIds.filter(id => id !== entryId));
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onListEntryMoveUp(position) {
 | 
					 | 
				
			||||||
        const owner = this.getFormStateOwner();
 | 
					 | 
				
			||||||
        const entryIds = owner.getFormValue(this.props.id);
 | 
					 | 
				
			||||||
        owner.updateFormValue(this.props.id, [...entryIds.slice(0, position - 1), entryIds[position], entryIds[position - 1], ...entryIds.slice(position + 1)]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onListEntryMoveDown(position) {
 | 
					 | 
				
			||||||
        const owner = this.getFormStateOwner();
 | 
					 | 
				
			||||||
        const entryIds = owner.getFormValue(this.props.id);
 | 
					 | 
				
			||||||
        owner.updateFormValue(this.props.id, [...entryIds.slice(0, position), entryIds[position + 1], entryIds[position], ...entryIds.slice(position + 2)]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    render() {
 | 
					 | 
				
			||||||
        const props = this.props;
 | 
					 | 
				
			||||||
        const owner = this.getFormStateOwner();
 | 
					 | 
				
			||||||
        const id = props.id;
 | 
					 | 
				
			||||||
        const t = props.t;
 | 
					 | 
				
			||||||
        const withOrder = props.withOrder;
 | 
					 | 
				
			||||||
        const entries = [];
 | 
					 | 
				
			||||||
        const entryIds = owner.getFormValue(id) || [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const entryButtonsStyles = withOrder ? cudStyles.entryButtonsWithOrder : cudStyles.entryButtons;
 | 
					 | 
				
			||||||
        for (let pos = 0; pos < entryIds.length; pos++) {
 | 
					 | 
				
			||||||
            const entryId = entryIds[pos];
 | 
					 | 
				
			||||||
            const elementId = this.getFormValueId(entryId);
 | 
					 | 
				
			||||||
            entries.push(
 | 
					 | 
				
			||||||
                <div key={entryId}
 | 
					 | 
				
			||||||
                     className={cudStyles.entry + (withOrder ? ' ' + cudStyles.withOrder : '') + ' ' + cudStyles.entryWithButtons}>
 | 
					 | 
				
			||||||
                    <div className={entryButtonsStyles}>
 | 
					 | 
				
			||||||
                        <Button
 | 
					 | 
				
			||||||
                            className="btn-secondary"
 | 
					 | 
				
			||||||
                            icon={`trash-alt ${withOrder ? "" : "fa-2x"}`}
 | 
					 | 
				
			||||||
                            title={t('remove')}
 | 
					 | 
				
			||||||
                            onClickAsync={() => this.onRemoveSetEntry(entryId)}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                        {withOrder &&
 | 
					 | 
				
			||||||
                        <Button
 | 
					 | 
				
			||||||
                            className="btn-secondary"
 | 
					 | 
				
			||||||
                            icon="plus"
 | 
					 | 
				
			||||||
                            title={t('Insert new entry before this one')}
 | 
					 | 
				
			||||||
                            onClickAsync={() => this.onAddListEntry(pos)}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        {withOrder && pos > 0 &&
 | 
					 | 
				
			||||||
                        <Button
 | 
					 | 
				
			||||||
                            className="btn-secondary"
 | 
					 | 
				
			||||||
                            icon="chevron-up"
 | 
					 | 
				
			||||||
                            title={t('Move up')}
 | 
					 | 
				
			||||||
                            onClickAsync={() => this.onListEntryMoveUp(pos)}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        {withOrder && pos < entryIds.length - 1 &&
 | 
					 | 
				
			||||||
                        <Button
 | 
					 | 
				
			||||||
                            className="btn-secondary"
 | 
					 | 
				
			||||||
                            icon="chevron-down"
 | 
					 | 
				
			||||||
                            title={t('Move down')}
 | 
					 | 
				
			||||||
                            onClickAsync={() => this.onListEntryMoveDown(pos)}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <div className={cudStyles.entryContent}>
 | 
					 | 
				
			||||||
                        {React.cloneElement(this.props.entryElement, {id: elementId})}
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return (
 | 
					 | 
				
			||||||
            <Fieldset id={id} className={props.classname} help={props.help} flat={props.flat} label={props.label}>
 | 
					 | 
				
			||||||
                {entries}
 | 
					 | 
				
			||||||
                <div key="newEntry" className={cudStyles.newEntry}>
 | 
					 | 
				
			||||||
                    <Button
 | 
					 | 
				
			||||||
                        className="btn-secondary"
 | 
					 | 
				
			||||||
                        icon="plus"
 | 
					 | 
				
			||||||
                        label={t('Add entry')}
 | 
					 | 
				
			||||||
                        onClickAsync={() => this.onAddListEntry(entryIds.length)}
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
            </Fieldset>
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@withComponentMixins([
 | 
					@withComponentMixins([
 | 
				
			||||||
    withFormStateOwner
 | 
					    withFormStateOwner
 | 
				
			||||||
])
 | 
					])
 | 
				
			||||||
| 
						 | 
					@ -1200,6 +997,7 @@ class TableSelect extends Component {
 | 
				
			||||||
        help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
 | 
					        help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
 | 
				
			||||||
        format: PropTypes.string,
 | 
					        format: PropTypes.string,
 | 
				
			||||||
        disabled: PropTypes.bool,
 | 
					        disabled: PropTypes.bool,
 | 
				
			||||||
 | 
					        withClear: PropTypes.bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pageLength: PropTypes.number
 | 
					        pageLength: PropTypes.number
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -1245,6 +1043,15 @@ class TableSelect extends Component {
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async clear() {
 | 
				
			||||||
 | 
					        const owner = this.getFormStateOwner();
 | 
				
			||||||
 | 
					        if (this.props.selectMode === TableSelectMode.SINGLE && !this.props.selectionAsArray) {
 | 
				
			||||||
 | 
					            owner.updateFormValue(this.props.id, null);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            owner.updateFormValue(this.props.id, []);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    refresh() {
 | 
					    refresh() {
 | 
				
			||||||
        this.table.refresh();
 | 
					        this.table.refresh();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -1256,6 +1063,8 @@ class TableSelect extends Component {
 | 
				
			||||||
        const htmlId = 'form_' + id;
 | 
					        const htmlId = 'form_' + id;
 | 
				
			||||||
        const t = props.t;
 | 
					        const t = props.t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const selection = owner.getFormValue(id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (props.dropdown) {
 | 
					        if (props.dropdown) {
 | 
				
			||||||
            const className = owner.addFormValidationClass('form-control', id);
 | 
					            const className = owner.addFormValidationClass('form-control', id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1271,6 +1080,7 @@ class TableSelect extends Component {
 | 
				
			||||||
                        {!props.disabled &&
 | 
					                        {!props.disabled &&
 | 
				
			||||||
                        <div className="input-group-append">
 | 
					                        <div className="input-group-append">
 | 
				
			||||||
                            <Button label={t('select')} className="btn-secondary" onClickAsync={::this.toggleOpen}/>
 | 
					                            <Button label={t('select')} className="btn-secondary" onClickAsync={::this.toggleOpen}/>
 | 
				
			||||||
 | 
					                            {props.withClear && selection && <Button icon="times" title={t('Clear')} className="btn-secondary" onClickAsync={::this.clear}/>}
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
| 
						 | 
					@ -1285,7 +1095,7 @@ class TableSelect extends Component {
 | 
				
			||||||
                               selectionAsArray={this.props.selectionAsArray}
 | 
					                               selectionAsArray={this.props.selectionAsArray}
 | 
				
			||||||
                               withHeader={props.withHeader}
 | 
					                               withHeader={props.withHeader}
 | 
				
			||||||
                               selectionKeyIndex={props.selectionKeyIndex}
 | 
					                               selectionKeyIndex={props.selectionKeyIndex}
 | 
				
			||||||
                               selection={owner.getFormValue(id)}
 | 
					                               selection={selection}
 | 
				
			||||||
                               onSelectionDataAsync={::this.onSelectionDataAsync}
 | 
					                               onSelectionDataAsync={::this.onSelectionDataAsync}
 | 
				
			||||||
                               onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
 | 
					                               onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
| 
						 | 
					@ -1305,7 +1115,7 @@ class TableSelect extends Component {
 | 
				
			||||||
                               selectionAsArray={this.props.selectionAsArray}
 | 
					                               selectionAsArray={this.props.selectionAsArray}
 | 
				
			||||||
                               withHeader={props.withHeader}
 | 
					                               withHeader={props.withHeader}
 | 
				
			||||||
                               selectionKeyIndex={props.selectionKeyIndex}
 | 
					                               selectionKeyIndex={props.selectionKeyIndex}
 | 
				
			||||||
                               selection={owner.getFormValue(id)}
 | 
					                               selection={selection}
 | 
				
			||||||
                               onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
 | 
					                               onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -211,7 +211,7 @@ function renderFrameWithContent(t, panelInFullScreen, showSidebar, primaryMenu,
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <footer key="appFooter" className="app-footer">
 | 
					                <footer key="appFooter" className="app-footer">
 | 
				
			||||||
                    <div className="text-muted">© 2018 <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/Mailtrain-org/mailtrain">{t('sourceOnGitHub')}</a></div>
 | 
					                    <div className="text-muted">© 2020 <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/Mailtrain-org/mailtrain">{t('sourceOnGitHub')}</a></div>
 | 
				
			||||||
                </footer>
 | 
					                </footer>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,7 +39,7 @@ export function getTagLanguages(t) {
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEMPLATE) {
 | 
					export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEMPLATE, allowEmpty = false) {
 | 
				
			||||||
    // The prefix is used to to enable use within other forms (i.e. campaign form)
 | 
					    // The prefix is used to to enable use within other forms (i.e. campaign form)
 | 
				
			||||||
    const templateTypes = {};
 | 
					    const templateTypes = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -97,7 +97,9 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
 | 
				
			||||||
                    dataUrl={`rest/mosaico-templates-by-tag-language-table/${tagLanguageKey}`}
 | 
					                    dataUrl={`rest/mosaico-templates-by-tag-language-table/${tagLanguageKey}`}
 | 
				
			||||||
                    columns={mosaicoTemplatesColumns}
 | 
					                    columns={mosaicoTemplatesColumns}
 | 
				
			||||||
                    selectionLabelIndex={1}
 | 
					                    selectionLabelIndex={1}
 | 
				
			||||||
                    disabled={isEdit}/>
 | 
					                    disabled={isEdit}
 | 
				
			||||||
 | 
					                    withClear={allowEmpty}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                return null;
 | 
					                return null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -169,7 +171,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        validate: state => {
 | 
					        validate: state => {
 | 
				
			||||||
            const mosaicoTemplate = state.getIn([prefix + 'mosaicoTemplate', 'value']);
 | 
					            const mosaicoTemplate = state.getIn([prefix + 'mosaicoTemplate', 'value']);
 | 
				
			||||||
            if (!mosaicoTemplate) {
 | 
					            if (!allowEmpty && !mosaicoTemplate) {
 | 
				
			||||||
                state.setIn([prefix + 'mosaicoTemplate', 'error'], t('mosaicoTemplateMustBeSelected'));
 | 
					                state.setIn([prefix + 'mosaicoTemplate', 'error'], t('mosaicoTemplateMustBeSelected'));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -298,7 +298,7 @@ defaultRoles:
 | 
				
			||||||
        sendConfiguration: [viewPublic, viewPrivate, edit, delete, share, sendWithoutOverrides, sendWithAllowedOverrides, sendWithAnyOverrides]
 | 
					        sendConfiguration: [viewPublic, viewPrivate, edit, delete, share, sendWithoutOverrides, sendWithAllowedOverrides, sendWithAnyOverrides]
 | 
				
			||||||
        list: [view, edit, delete, share, viewFields, manageFields, viewSubscriptions, viewTestSubscriptions, manageSubscriptions, viewSegments, manageSegments, viewImports, manageImports, send, sendToTestUsers]
 | 
					        list: [view, edit, delete, share, viewFields, manageFields, viewSubscriptions, viewTestSubscriptions, manageSubscriptions, viewSegments, manageSegments, viewImports, manageImports, send, sendToTestUsers]
 | 
				
			||||||
        customForm: [view, edit, delete, share]
 | 
					        customForm: [view, edit, delete, share]
 | 
				
			||||||
        channel: [view, edit, delete, share]
 | 
					        channel: [view, edit, delete, createCampaign, share]
 | 
				
			||||||
        campaign: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, sendToTestUsers, viewStats, fetchRss]
 | 
					        campaign: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, sendToTestUsers, viewStats, fetchRss]
 | 
				
			||||||
        template: [view, edit, delete, share, viewFiles, manageFiles, sendToTestUsers]
 | 
					        template: [view, edit, delete, share, viewFiles, manageFiles, sendToTestUsers]
 | 
				
			||||||
        report: [view, edit, delete, share, execute, viewContent, viewOutput]
 | 
					        report: [view, edit, delete, share, execute, viewContent, viewOutput]
 | 
				
			||||||
| 
						 | 
					@ -308,13 +308,13 @@ defaultRoles:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    campaignsAdmin:
 | 
					    campaignsAdmin:
 | 
				
			||||||
      name: Campaigns Admin
 | 
					      name: Campaigns Admin
 | 
				
			||||||
      description: In the respective namespace, the user has all permissions for managing lists, templates and campaigns and the permission to send to send configurations.
 | 
					      description: In the respective namespace, the user has all permissions for managing lists, channels, templates and campaigns and the permission to send to send configurations.
 | 
				
			||||||
      permissions: [view, edit, delete, share, createNamespace, createList, createCustomForm, createReport, createTemplate, createMosaicoTemplate, createChannel, createCampaign]
 | 
					      permissions: [view, edit, delete, share, createNamespace, createList, createCustomForm, createReport, createTemplate, createMosaicoTemplate, createChannel, createCampaign]
 | 
				
			||||||
      children:
 | 
					      children:
 | 
				
			||||||
        sendConfiguration: [viewPublic, sendWithoutOverrides, sendWithAllowedOverrides]
 | 
					        sendConfiguration: [viewPublic, sendWithoutOverrides, sendWithAllowedOverrides]
 | 
				
			||||||
        list: [view, edit, delete, share, viewFields, manageFields, viewSubscriptions, viewTestSubscriptions, manageSubscriptions, viewSegments, manageSegments, viewImports, manageImports, send, sendToTestUsers]
 | 
					        list: [view, edit, delete, share, viewFields, manageFields, viewSubscriptions, viewTestSubscriptions, manageSubscriptions, viewSegments, manageSegments, viewImports, manageImports, send, sendToTestUsers]
 | 
				
			||||||
        customForm: [view, edit, delete, share]
 | 
					        customForm: [view, edit, delete, share]
 | 
				
			||||||
        channel: [view, edit, delete, share]
 | 
					        channel: [view, edit, delete, createCampaign, share]
 | 
				
			||||||
        campaign: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, sendToTestUsers, viewStats, fetchRss]
 | 
					        campaign: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, sendToTestUsers, viewStats, fetchRss]
 | 
				
			||||||
        template: [view, edit, delete, share, viewFiles, manageFiles, sendToTestUsers]
 | 
					        template: [view, edit, delete, share, viewFiles, manageFiles, sendToTestUsers]
 | 
				
			||||||
        report: [view, edit, delete, share, execute, viewContent, viewOutput]
 | 
					        report: [view, edit, delete, share, execute, viewContent, viewOutput]
 | 
				
			||||||
| 
						 | 
					@ -325,7 +325,7 @@ defaultRoles:
 | 
				
			||||||
    campaignsCreator:
 | 
					    campaignsCreator:
 | 
				
			||||||
      name: Campaigns Creator
 | 
					      name: Campaigns Creator
 | 
				
			||||||
      description: In the respective namespace, the user has all permissions to create and manage templates and campaigns. The user can also read public data about send configurations and use Mosaico templates in the namespace.
 | 
					      description: In the respective namespace, the user has all permissions to create and manage templates and campaigns. The user can also read public data about send configurations and use Mosaico templates in the namespace.
 | 
				
			||||||
      permissions: [view, createTemplate, createChannel, createCampaign]
 | 
					      permissions: [view, createTemplate, createCampaign]
 | 
				
			||||||
      children:
 | 
					      children:
 | 
				
			||||||
        sendConfiguration: [viewPublic]
 | 
					        sendConfiguration: [viewPublic]
 | 
				
			||||||
        channel: [view]
 | 
					        channel: [view]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,14 +11,14 @@ async function validateEntity(tx, entity) {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function validateMove(context, entity, existing, entityTypeId, createOperation, deleteOperation) {
 | 
					async function validateMoveTx(tx, context, entity, existing, entityTypeId, createOperation, deleteOperation) {
 | 
				
			||||||
    if (existing.namespace !== entity.namespace) {
 | 
					    if (existing.namespace !== entity.namespace) {
 | 
				
			||||||
        await shares.enforceEntityPermission(context, 'namespace', entity.namespace, createOperation);
 | 
					        await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, createOperation);
 | 
				
			||||||
        await shares.enforceEntityPermission(context, entityTypeId, entity.id, deleteOperation);
 | 
					        await shares.enforceEntityPermissionTx(tx, context, entityTypeId, entity.id, deleteOperation);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    validateEntity,
 | 
					    validateEntity,
 | 
				
			||||||
    validateMove
 | 
					    validateMoveTx
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -357,7 +357,7 @@ async function rawGetByTx(tx, key, id) {
 | 
				
			||||||
        .leftJoin('campaign_lists', 'campaigns.id', 'campaign_lists.campaign')
 | 
					        .leftJoin('campaign_lists', 'campaigns.id', 'campaign_lists.campaign')
 | 
				
			||||||
        .groupBy('campaigns.id')
 | 
					        .groupBy('campaigns.id')
 | 
				
			||||||
        .select([
 | 
					        .select([
 | 
				
			||||||
            'campaigns.id', 'campaigns.cid', 'campaigns.name', 'campaigns.description', 'campaigns.namespace', 'campaigns.status', 'campaigns.type', 'campaigns.source',
 | 
					            'campaigns.id', 'campaigns.cid', 'campaigns.name', 'campaigns.description', 'campaigns.channel', 'campaigns.namespace', 'campaigns.status', 'campaigns.type', 'campaigns.source',
 | 
				
			||||||
            'campaigns.send_configuration', 'campaigns.from_name_override', 'campaigns.from_email_override', 'campaigns.reply_to_override', 'campaigns.subject',
 | 
					            'campaigns.send_configuration', 'campaigns.from_name_override', 'campaigns.from_email_override', 'campaigns.reply_to_override', 'campaigns.subject',
 | 
				
			||||||
            'campaigns.data', 'campaigns.click_tracking_disabled', 'campaigns.open_tracking_disabled', 'campaigns.unsubscribe_url', 'campaigns.scheduled',
 | 
					            'campaigns.data', 'campaigns.click_tracking_disabled', 'campaigns.open_tracking_disabled', 'campaigns.unsubscribe_url', 'campaigns.scheduled',
 | 
				
			||||||
            'campaigns.delivered', 'campaigns.unsubscribed', 'campaigns.bounced', 'campaigns.complained', 'campaigns.blacklisted', 'campaigns.opened', 'campaigns.clicks',
 | 
					            'campaigns.delivered', 'campaigns.unsubscribed', 'campaigns.bounced', 'campaigns.complained', 'campaigns.blacklisted', 'campaigns.opened', 'campaigns.clicks',
 | 
				
			||||||
| 
						 | 
					@ -412,6 +412,7 @@ async function getByIdTx(tx, context, id, withPermissions = true, content = Cont
 | 
				
			||||||
    } else if (content === Content.ONLY_SOURCE_CUSTOM) {
 | 
					    } else if (content === Content.ONLY_SOURCE_CUSTOM) {
 | 
				
			||||||
        entity = {
 | 
					        entity = {
 | 
				
			||||||
            id: entity.id,
 | 
					            id: entity.id,
 | 
				
			||||||
 | 
					            channel: entity.channel,
 | 
				
			||||||
            send_configuration: entity.send_configuration,
 | 
					            send_configuration: entity.send_configuration,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            data: {
 | 
					            data: {
 | 
				
			||||||
| 
						 | 
					@ -454,6 +455,8 @@ async function _validateAndPreprocess(tx, context, entity, isCreate, content) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (entity.source === CampaignSource.TEMPLATE || entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
					            if (entity.source === CampaignSource.TEMPLATE || entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
				
			||||||
                await shares.enforceEntityPermissionTx(tx, context, 'template', entity.data.sourceTemplate, 'view');
 | 
					                await shares.enforceEntityPermissionTx(tx, context, 'template', entity.data.sourceTemplate, 'view');
 | 
				
			||||||
 | 
					            } else if (entity.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
 | 
				
			||||||
 | 
					                await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.data.sourceCampaign, 'view');
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            enforce(Number.isInteger(entity.source));
 | 
					            enforce(Number.isInteger(entity.source));
 | 
				
			||||||
| 
						 | 
					@ -481,6 +484,10 @@ async function _createTx(tx, context, entity, content) {
 | 
				
			||||||
    return await knex.transaction(async tx => {
 | 
					    return await knex.transaction(async tx => {
 | 
				
			||||||
        await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createCampaign');
 | 
					        await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createCampaign');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (entity.channel) {
 | 
				
			||||||
 | 
					            await shares.enforceEntityPermissionTx(tx, context, 'channel', entity.channel, 'createCampaign');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let copyFilesFrom = null;
 | 
					        let copyFilesFrom = null;
 | 
				
			||||||
        if (entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
					        if (entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
				
			||||||
            copyFilesFrom = {
 | 
					            copyFilesFrom = {
 | 
				
			||||||
| 
						 | 
					@ -570,6 +577,13 @@ async function createRssTx(tx, context, entity) {
 | 
				
			||||||
    return await _createTx(tx, context, entity, Content.RSS_ENTRY);
 | 
					    return await _createTx(tx, context, entity, Content.RSS_ENTRY);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function _validateChannelMoveTx(tx, context, entity, existing) {
 | 
				
			||||||
 | 
					    if (existing.channel !== entity.channel) {
 | 
				
			||||||
 | 
					        await shares.enforceEntityPermission(context, 'channel', entity.channel, 'createCampaign');
 | 
				
			||||||
 | 
					        await shares.enforceEntityPermission(context, 'campaign', entity.id, 'delete');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function updateWithConsistencyCheck(context, entity, content) {
 | 
					async function updateWithConsistencyCheck(context, entity, content) {
 | 
				
			||||||
    await knex.transaction(async tx => {
 | 
					    await knex.transaction(async tx => {
 | 
				
			||||||
        await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.id, 'edit');
 | 
					        await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.id, 'edit');
 | 
				
			||||||
| 
						 | 
					@ -585,11 +599,13 @@ async function updateWithConsistencyCheck(context, entity, content) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let filteredEntity = filterObject(entity, allowedKeysUpdate);
 | 
					        let filteredEntity = filterObject(entity, allowedKeysUpdate);
 | 
				
			||||||
        if (content === Content.ALL) {
 | 
					        if (content === Content.ALL) {
 | 
				
			||||||
            await namespaceHelpers.validateMove(context, entity, existing, 'campaign', 'createCampaign', 'delete');
 | 
					            await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'campaign', 'createCampaign', 'delete');
 | 
				
			||||||
 | 
					            await _validateChannelMoveTx(tx, context, entity, existing);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        } else if (content === Content.WITHOUT_SOURCE_CUSTOM) {
 | 
					        } else if (content === Content.WITHOUT_SOURCE_CUSTOM) {
 | 
				
			||||||
            filteredEntity.data.sourceCustom = existing.data.sourceCustom;
 | 
					            filteredEntity.data.sourceCustom = existing.data.sourceCustom;
 | 
				
			||||||
            await namespaceHelpers.validateMove(context, entity, existing, 'campaign', 'createCampaign', 'delete');
 | 
					            await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'campaign', 'createCampaign', 'delete');
 | 
				
			||||||
 | 
					            await _validateChannelMoveTx(tx, context, entity, existing);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        } else if (content === Content.ONLY_SOURCE_CUSTOM) {
 | 
					        } else if (content === Content.ONLY_SOURCE_CUSTOM) {
 | 
				
			||||||
            const data = existing.data;
 | 
					            const data = existing.data;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,8 +16,8 @@ const dependencyHelpers = require('../lib/dependency-helpers');
 | 
				
			||||||
const {EntityActivityType, CampaignActivityType} = require('../../shared/activity-log');
 | 
					const {EntityActivityType, CampaignActivityType} = require('../../shared/activity-log');
 | 
				
			||||||
const activityLog = require('../lib/activity-log');
 | 
					const activityLog = require('../lib/activity-log');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const allowedKeys = ['name', 'description', 'namespace', 'cpg_name', 'cpg_description',
 | 
					const allowedKeys = new Set(['name', 'description', 'namespace', 'cpg_name', 'cpg_description',
 | 
				
			||||||
    'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url', 'source'];
 | 
					    'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url', 'source']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function hash(entity) {
 | 
					function hash(entity) {
 | 
				
			||||||
| 
						 | 
					@ -43,12 +43,27 @@ async function listDTAjax(context, params) {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function _getByTx(tx, key, id, withPermissions = true) {
 | 
					async function listWithCreateCampaignPermissionDTAjax(context, params) {
 | 
				
			||||||
 | 
					    return await dtHelpers.ajaxListWithPermissions(
 | 
				
			||||||
 | 
					        context,
 | 
				
			||||||
 | 
					        [{ entityTypeId: 'channel', requiredOperations: ['createCampaign'] }],
 | 
				
			||||||
 | 
					        params,
 | 
				
			||||||
 | 
					        builder => {
 | 
				
			||||||
 | 
					            builder = builder.from('channels')
 | 
				
			||||||
 | 
					                .innerJoin('namespaces', 'namespaces.id', 'channels.namespace');
 | 
				
			||||||
 | 
					            return builder;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        ['channels.id', 'channels.name', 'channels.cid', 'channels.description', 'namespaces.name']
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function _getByTx(tx, context, key, id, withPermissions = true) {
 | 
				
			||||||
    const entity = await tx('channels').where('channels.' + key, id)
 | 
					    const entity = await tx('channels').where('channels.' + key, id)
 | 
				
			||||||
        .leftJoin('channel_lists', 'channels.id', 'channel_lists.channel')
 | 
					        .leftJoin('channel_lists', 'channels.id', 'channel_lists.channel')
 | 
				
			||||||
        .groupBy('channels.id')
 | 
					        .groupBy('channels.id')
 | 
				
			||||||
        .select([
 | 
					        .select([
 | 
				
			||||||
            'channels.id', 'channels.name', 'channels.cid', 'channels.description', 'channels.namespace', 'channels.source',
 | 
					            'channels.id', 'channels.name', 'channels.cid', 'channels.description', 'channels.namespace', 'channels.cpg_name', 'channels.cpg_description', 'channels.source',
 | 
				
			||||||
            'channels.send_configuration', 'channels.from_name_override', 'channels.from_email_override', 'channels.reply_to_override', 'channels.subject',
 | 
					            'channels.send_configuration', 'channels.from_name_override', 'channels.from_email_override', 'channels.reply_to_override', 'channels.subject',
 | 
				
			||||||
            'channels.data', 'channels.click_tracking_disabled', 'channels.open_tracking_disabled', 'channels.unsubscribe_url',
 | 
					            'channels.data', 'channels.click_tracking_disabled', 'channels.open_tracking_disabled', 'channels.unsubscribe_url',
 | 
				
			||||||
            knex.raw(`GROUP_CONCAT(CONCAT_WS(\':\', channel_lists.list, channel_lists.segment) ORDER BY channel_lists.id SEPARATOR \';\') as lists`)
 | 
					            knex.raw(`GROUP_CONCAT(CONCAT_WS(\':\', channel_lists.list, channel_lists.segment) ORDER BY channel_lists.id SEPARATOR \';\') as lists`)
 | 
				
			||||||
| 
						 | 
					@ -82,7 +97,7 @@ async function _getByTx(tx, key, id, withPermissions = true) {
 | 
				
			||||||
async function getByIdTx(tx, context, id, withPermissions = true) {
 | 
					async function getByIdTx(tx, context, id, withPermissions = true) {
 | 
				
			||||||
    await shares.enforceEntityPermissionTx(tx, context, 'channel', id, 'view');
 | 
					    await shares.enforceEntityPermissionTx(tx, context, 'channel', id, 'view');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return await _getByTx(tx, 'id', id, withPermissions);
 | 
					    return await _getByTx(tx, context, 'id', id, withPermissions);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function getById(context, id, withPermissions = true) {
 | 
					async function getById(context, id, withPermissions = true) {
 | 
				
			||||||
| 
						 | 
					@ -97,12 +112,13 @@ async function _validateAndPreprocess(tx, context, entity, isCreate) {
 | 
				
			||||||
    if (entity.source !== null) {
 | 
					    if (entity.source !== null) {
 | 
				
			||||||
        enforce(Number.isInteger(entity.source));
 | 
					        enforce(Number.isInteger(entity.source));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
					        if (entity.source === CampaignSource.TEMPLATE || entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
 | 
				
			||||||
            await shares.enforceEntityPermissionTx(tx, context, 'template', entity.data.sourceTemplate, 'view');
 | 
					            await shares.enforceEntityPermissionTx(tx, context, 'template', entity.data.sourceTemplate, 'view');
 | 
				
			||||||
        } else if (entity.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
 | 
					        } else if (entity.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
 | 
				
			||||||
            await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.data.sourceCampaign, 'view');
 | 
					            await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.data.sourceCampaign, 'view');
 | 
				
			||||||
        } else if (entity.source === CampaignSource.CUSTOM) {
 | 
					        } else if (entity.source === CampaignSource.CUSTOM) {
 | 
				
			||||||
            enforce(allTagLanguages.includes(entity.data.sourceCustom.tag_language), `Invalid tag language '${entity.data.sourceCustom.tag_language}'`);
 | 
					            enforce(allTagLanguages.includes(entity.data.sourceCustom.tag_language), `Invalid tag language '${entity.data.sourceCustom.tag_language}'`);
 | 
				
			||||||
 | 
					        } else if (entity.source === CampaignSource.URL) {
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            enforce(false, 'Unknown channel source');
 | 
					            enforce(false, 'Unknown channel source');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -156,7 +172,7 @@ async function updateWithConsistencyCheck(context, entity) {
 | 
				
			||||||
    await knex.transaction(async tx => {
 | 
					    await knex.transaction(async tx => {
 | 
				
			||||||
        await shares.enforceEntityPermissionTx(tx, context, 'channel', entity.id, 'edit');
 | 
					        await shares.enforceEntityPermissionTx(tx, context, 'channel', entity.id, 'edit');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const existing = await _getByTx(tx, 'id', entity.id, false);
 | 
					        const existing = await _getByTx(tx, context, 'id', entity.id, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const existingHash = hash(existing);
 | 
					        const existingHash = hash(existing);
 | 
				
			||||||
        if (existingHash !== entity.originalHash) {
 | 
					        if (existingHash !== entity.originalHash) {
 | 
				
			||||||
| 
						 | 
					@ -166,7 +182,7 @@ async function updateWithConsistencyCheck(context, entity) {
 | 
				
			||||||
        await _validateAndPreprocess(tx, context, entity, false);
 | 
					        await _validateAndPreprocess(tx, context, entity, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let filteredEntity = filterObject(entity, allowedKeys);
 | 
					        let filteredEntity = filterObject(entity, allowedKeys);
 | 
				
			||||||
        await namespaceHelpers.validateMove(context, entity, existing, 'channel', 'createCampaign', 'delete');
 | 
					        await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'channel', 'createCampaign', 'delete');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await tx('channel_lists').where('channel', entity.id).del();
 | 
					        await tx('channel_lists').where('channel', entity.id).del();
 | 
				
			||||||
        await tx('channel_lists').insert(entity.lists.map(x => ({channel: entity.id, ...x})));
 | 
					        await tx('channel_lists').insert(entity.lists.map(x => ({channel: entity.id, ...x})));
 | 
				
			||||||
| 
						 | 
					@ -197,8 +213,8 @@ async function remove(context, id) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports.hash = hash;
 | 
					module.exports.hash = hash;
 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports.listDTAjax = listDTAjax;
 | 
					module.exports.listDTAjax = listDTAjax;
 | 
				
			||||||
 | 
					module.exports.listWithCreateCampaignPermissionDTAjax = listWithCreateCampaignPermissionDTAjax;
 | 
				
			||||||
module.exports.getByIdTx = getByIdTx;
 | 
					module.exports.getByIdTx = getByIdTx;
 | 
				
			||||||
module.exports.getById = getById;
 | 
					module.exports.getById = getById;
 | 
				
			||||||
module.exports.create = create;
 | 
					module.exports.create = create;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -169,7 +169,7 @@ async function updateWithConsistencyCheck(context, entity) {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await namespaceHelpers.validateEntity(tx, entity);
 | 
					        await namespaceHelpers.validateEntity(tx, entity);
 | 
				
			||||||
        await namespaceHelpers.validateMove(context, entity, existing, 'customForm', 'createCustomForm', 'delete');
 | 
					        await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'customForm', 'createCustomForm', 'delete');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const form = filterObject(entity, allowedFormKeys);
 | 
					        const form = filterObject(entity, allowedFormKeys);
 | 
				
			||||||
        enforce(!Object.keys(checkForMjmlErrors(form)).length, 'Error(s) in form templates');
 | 
					        enforce(!Object.keys(checkForMjmlErrors(form)).length, 'Error(s) in form templates');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -263,7 +263,7 @@ async function updateWithConsistencyCheck(context, entity) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await _validateAndPreprocess(tx, entity);
 | 
					        await _validateAndPreprocess(tx, entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await namespaceHelpers.validateMove(context, entity, existing, 'list', 'createList', 'delete');
 | 
					        await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'list', 'createList', 'delete');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await tx('lists').where('id', entity.id).update(filterObject(entity, allowedKeys));
 | 
					        await tx('lists').where('id', entity.id).update(filterObject(entity, allowedKeys));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,7 +89,7 @@ async function updateWithConsistencyCheck(context, entity) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await _validateAndPreprocess(tx, entity);
 | 
					        await _validateAndPreprocess(tx, entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await namespaceHelpers.validateMove(context, entity, existing, 'mosaicoTemplate', 'createMosaicoTemplate', 'delete');
 | 
					        await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'mosaicoTemplate', 'createMosaicoTemplate', 'delete');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await tx('mosaico_templates').where('id', entity.id).update(filterObject(entity, allowedKeys));
 | 
					        await tx('mosaico_templates').where('id', entity.id).update(filterObject(entity, allowedKeys));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -198,7 +198,7 @@ async function updateWithConsistencyCheck(context, entity) {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // namespaceHelpers.validateEntity is not needed here because it is part of the tree traversal check below
 | 
					        // namespaceHelpers.validateEntity is not needed here because it is part of the tree traversal check below
 | 
				
			||||||
        await namespaceHelpers.validateMove(context, entity, existing, 'namespace', 'createNamespace', 'delete');
 | 
					        await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'namespace', 'createNamespace', 'delete');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let iter = entity;
 | 
					        let iter = entity;
 | 
				
			||||||
        while (iter.namespace != null) {
 | 
					        while (iter.namespace != null) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,7 +66,7 @@ async function updateWithConsistencyCheck(context, entity) {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await namespaceHelpers.validateEntity(tx, entity);
 | 
					        await namespaceHelpers.validateEntity(tx, entity);
 | 
				
			||||||
        await namespaceHelpers.validateMove(context, entity, existing, 'reportTemplate', 'createReportTemplate', 'delete');
 | 
					        await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'reportTemplate', 'createReportTemplate', 'delete');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await tx('report_templates').where('id', entity.id).update(filterObject(entity, allowedKeys));
 | 
					        await tx('report_templates').where('id', entity.id).update(filterObject(entity, allowedKeys));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,7 +102,7 @@ async function updateWithConsistencyCheck(context, entity) {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await namespaceHelpers.validateEntity(tx, entity);
 | 
					        await namespaceHelpers.validateEntity(tx, entity);
 | 
				
			||||||
        await namespaceHelpers.validateMove(context, entity, existing, 'report', 'createReport', 'delete');
 | 
					        await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'report', 'createReport', 'delete');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        entity.params = JSON.stringify(entity.params);
 | 
					        entity.params = JSON.stringify(entity.params);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -155,7 +155,7 @@ async function updateWithConsistencyCheck(context, entity) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await _validateAndPreprocess(tx, entity);
 | 
					        await _validateAndPreprocess(tx, entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await namespaceHelpers.validateMove(context, entity, existing, 'sendConfiguration', 'createSendConfiguration', 'delete');
 | 
					        await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'sendConfiguration', 'createSendConfiguration', 'delete');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await tx('send_configurations').where('id', entity.id).update(filterObject(entity, allowedKeys));
 | 
					        await tx('send_configurations').where('id', entity.id).update(filterObject(entity, allowedKeys));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -132,7 +132,7 @@ async function updateWithConsistencyCheck(context, entity) {
 | 
				
			||||||
        await _validateAndPreprocess(tx, entity);
 | 
					        await _validateAndPreprocess(tx, entity);
 | 
				
			||||||
        entity.data = JSON.stringify(entity.data);
 | 
					        entity.data = JSON.stringify(entity.data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await namespaceHelpers.validateMove(context, entity, existing, 'template', 'createTemplate', 'delete');
 | 
					        await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'template', 'createTemplate', 'delete');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const filteredEntity = filterObject(entity, allowedKeys);
 | 
					        const filteredEntity = filterObject(entity, allowedKeys);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,10 @@ router.postAsync('/channels-table', passport.loggedIn, async (req, res) => {
 | 
				
			||||||
    return res.json(await channels.listDTAjax(req.context, req.body));
 | 
					    return res.json(await channels.listDTAjax(req.context, req.body));
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router.postAsync('/channels-with-create-campaign-permission-table', passport.loggedIn, async (req, res) => {
 | 
				
			||||||
 | 
					    return res.json(await channels.listWithCreateCampaignPermissionDTAjax(req.context, req.body));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.getAsync('/channels/:channelId', passport.loggedIn, async (req, res) => {
 | 
					router.getAsync('/channels/:channelId', passport.loggedIn, async (req, res) => {
 | 
				
			||||||
    const channel = await channels.getById(req.context, castToInteger(req.params.channelId), true);
 | 
					    const channel = await channels.getById(req.context, castToInteger(req.params.channelId), true);
 | 
				
			||||||
    channel.hash = channels.hash(channel);
 | 
					    channel.hash = channels.hash(channel);
 | 
				
			||||||
| 
						 | 
					@ -25,7 +29,7 @@ router.putAsync('/channels/:channelId', passport.loggedIn, passport.csrfProtecti
 | 
				
			||||||
    const entity = req.body;
 | 
					    const entity = req.body;
 | 
				
			||||||
    entity.id = castToInteger(req.params.channelId);
 | 
					    entity.id = castToInteger(req.params.channelId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await channels.updateWithConsistencyCheck(req.context);
 | 
					    await channels.updateWithConsistencyCheck(req.context, entity);
 | 
				
			||||||
    return res.json();
 | 
					    return res.json();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@
 | 
				
			||||||
        "node": ">=10.0.0"
 | 
					        "node": ">=10.0.0"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "dependencies": {
 | 
					    "dependencies": {
 | 
				
			||||||
        "zone-mta": "^2.2.1",
 | 
					        "zone-mta": "^2.3.0",
 | 
				
			||||||
        "zonemta-delivery-counters": "^1.0.1",
 | 
					        "zonemta-delivery-counters": "^1.0.1",
 | 
				
			||||||
        "zonemta-limiter": "^1.0.0",
 | 
					        "zonemta-limiter": "^1.0.0",
 | 
				
			||||||
        "zonemta-loop-breaker": "^1.0.2"
 | 
					        "zonemta-loop-breaker": "^1.0.2"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue