<commit_before>//##################################################################################################################
// #STRUCTUREBOARD#
/* global CMS */

(function ($) {
    'use strict';

    // CMS.$ will be passed for $
    $(function () {
        var placeholders = $('.cms-dragarea:not(.cms-clipboard-containers)');
        function actualizeEmptyPlaceholders() {
            placeholders.each(function () {
                var placeholder = $(this);
                if (placeholder
                    .find('> .cms-draggables')
                    .children('.cms-draggable:not(.cms-draggable-is-dragging)').length) {
                    placeholder.removeClass('cms-dragarea-empty');
                } else {
                    placeholder.addClass('cms-dragarea-empty');
                }
            });
        }

        function actualizePluginsCollapsibleStatus(els) {
            els.each(function () {
                var childList = $(this);
                var pluginDragItem = childList.closest('.cms-draggable').find('> .cms-dragitem');

                if (childList.children().length) {
                    pluginDragItem.addClass('cms-dragitem-collapsable cms-dragitem-expanded');
                } else {
                    pluginDragItem.removeClass('cms-dragitem-collapsable');
                }
            });
        }

        /*!
         * StructureBoard
         * handles drag & drop, mode switching and
         */
        CMS.StructureBoard = new CMS.Class({

            implement: [CMS.API.Helpers],

            options: {
                speed: 300
            },

            initialize: function (options) {
                this.options = $.extend(true, {}, this.options, options);
                this.config = CMS.config;
                this.settings = CMS.settings;

                // elements
                this._setupUI();

                // states
                this.click = 'click.cms';
                this.state = false;
                this.dragging = false;

                // setup initial stuff
                this._setup();

                // setup events
                this._events();
                actualizeEmptyPlaceholders();
            },

            _setupUI: function setupUI() {
                var container = $('.cms-structure');
                var toolbar = $('#cms-toolbar');
                this.ui = {
                    container: container,
                    content: $('.cms-structure-content'),
                    doc: $(document),
                    window: $(window),
                    toolbar: toolbar,
                    sortables: $('.cms-draggables'), // global scope to include clipboard
                    plugins: $('.cms-plugin'),
                    render_model: $('.cms-render-model'),
                    placeholders: $('.cms-placeholder'),
                    dragitems: $('.cms-draggable'),
                    dragareas: $('.cms-dragarea'),
                    clipboard: $('.cms-clipboard'),
                    toolbarModeSwitcher: toolbar.find('.cms-toolbar-item-cms-mode-switcher'),
                    toolbarModeLinks: toolbar.find('.cms-toolbar-item-cms-mode-switcher a')
                };
            },

            // initial methods
            _setup: function () {
                var that = this;
                // cancel if there are no dragareas
                if (!this.ui.dragareas.length) {
                    return false;
                }

                // cancel if there is no structure / content switcher
                if (!this.ui.toolbarModeSwitcher.length) {
                    return false;
                }

                // setup toolbar mode
                // FIXME this setTimeout is needed because
                // plugins are initialized after all the scripts are processed
                // which should be fixed btw. _resizeBoard wants plugins to be initialized,
                // otherwise throws errors
                setTimeout(function () {
                    if (that.settings.mode === 'structure') {
                        that.show(true);
                    } else {
                        // triggering hide here to switch proper classnames on switcher
                        that.hide(true);
                    }
                }, 0);

                // check if modes should be visible
                if (this.ui.placeholders.length) {
                    this.ui.toolbarModeSwitcher.show();
                }

                // add drag & drop functionality
                this._drag();
            },

            _events: function () {
                var that = this;
                var modes = that.ui.toolbarModeLinks;

                // show edit mode
                modes.eq(1).on(that.click, function (e) {
                    e.preventDefault();
                    // cancel if already active
                    if (that.settings.mode === 'edit') {
                        return false;
                    }
                    // otherwise hide
                    that.hide();
                });
                // show structure mode
                modes.eq(0).on(that.click, function (e) {
                    e.preventDefault();
                    // cancel if already active
                    if (that.settings.mode === 'structure') {
                        return false;
                    }
                    // otherwise show
                    that.show();
                });

                // keyboard handling
                // only if there is a structure / content switcher
                if (that.ui.toolbarModeSwitcher.length) {
                    that.ui.doc.on('keydown', function (e) {
                        // check if we have an important focus
                        var haveFocusedField = document.activeElement !== document.body;
                        if (e.keyCode === CMS.KEYS.SPACE && !haveFocusedField) {
                            e.preventDefault();
                            if (that.settings.mode === 'structure') {
                                that.hide();
                            } else if (that.settings.mode === 'edit') {
                                that.show();
                            }
                        }
                    });
                }
            },

            // public methods
            show: function (init) {
                // cancel show if live modus is active
                if (CMS.config.mode === 'live') {
                    return false;
                }

                // set active item
                var modes = this.ui.toolbarModeLinks;
                modes.removeClass('cms-btn-active').eq(0).addClass('cms-btn-active');

                // show clipboard
                this.ui.clipboard.fadeIn(this.options.speed);

                // apply new settings
                this.settings.mode = 'structure';
                if (!init) {
                    this.settings = this.setSettings(this.settings);
                }

                // ensure all elements are visible
                this.ui.dragareas.show();

                // show canvas
                this._showBoard();
            },

            hide: function (init) {
                // cancel show if live modus is active
                if (CMS.config.mode === 'live') {
                    return false;
                }

                // set active item
                var modes = this.ui.toolbarModeLinks;
                modes.removeClass('cms-btn-active').eq(1).addClass('cms-btn-active');

                // hide clipboard if in edit mode
                this.ui.container.find('.cms-clipboard').hide();

                // hide clipboard
                this.ui.clipboard.hide();

                this.settings.mode = 'edit';
                if (!init) {
                    this.settings = this.setSettings(this.settings);
                }

                // hide canvas
                this._hideBoard();
            },

            /**
             * gets the id of the element
             *
             * @param el jQuery element to get id from
             * @return String
             */
            getId: function (el) {
                // cancel if no element is defined
                if (el === undefined || el === null || el.length <= 0) {
                    return false;
                }

                var id = null;
                var cls = el.attr('class').split(' ')[1];

                if (el.hasClass('cms-plugin')) {
                    id = cls.replace('cms-plugin-', '');
                } else if (el.hasClass('cms-draggable')) {
                    id = cls.replace('cms-draggable-', '');
                } else if (el.hasClass('cms-placeholder')) {
                    id = cls.replace('cms-placeholder-', '');
                } else if (el.hasClass('cms-dragbar')) {
                    id = cls.replace('cms-dragbar-', '');
                } else if (el.hasClass('cms-dragarea')) {
                    id = cls.replace('cms-dragarea-', '');
                }

                return id;
            },

            getIds: function (els) {
                var that = this;
                var array = [];
                els.each(function () {
                    array.push(that.getId($(this)));
                });
                return array;
            },

            // private methods
            _showBoard: function () {
                var that = this;

                // show container
                this.ui.container.show();
                this.ui.dragareas.css('opacity', 1);

                this.ui.plugins.not(this.ui.render_model).hide();
                this.ui.placeholders.show();

                // attach event
                if (CMS.config.simpleStructureBoard) {
                    var content = this.ui.content;
                    var areas = content.find('.cms-dragarea');
                    // set correct css attributes for the new mode
                    content.addClass('cms-structure-content-simple');
                    areas.addClass('cms-dragarea-simple');
                    // lets reorder placeholders
                    areas.each(function (index, item) {
                        if ($(item).hasClass('cms-dragarea-static')) {
                            content.append(item);
                        }
                    });
                    // now lets get the first instance and add some padding
                    areas.filter('.cms-dragarea-static').eq(0).css('margin-top', '50px');
                } else {
                    this.ui.container.addClass('cms-structure-dynamic');
                    this.ui.window.on('resize.sideframe', function () {
                        that._resizeBoard();
                    }).trigger('resize.sideframe');
                }
            },

            _hideBoard: function () {
                // hide elements
                this.ui.container.hide();
                this.ui.plugins.show();
                this.ui.placeholders.hide();

                // detach event
                this.ui.window.off('resize.sideframe');

                this.ui.window.trigger('structureboard_hidden.sideframe');
                if (!CMS.config.simpleStructureBoard) {
                    this.ui.container.height(this.ui.doc.outerHeight());
                }
            },

            /*
             * @deprecated as of CMS 3.2
             */
            _resizeBoard: function () {
                // calculate placeholder position
                var id = null;
                var area = null;
                var min = null;
                var areaParentOffset = null;
                var that = this;

                // have to delay since height changes when toggling modes
                setTimeout(function () {
                    that.ui.container.height(that.ui.doc.outerHeight());
                }, 0);

                // start calculating
                this.ui.placeholders.each(function (index, item) {
                    item = $(item);
                    id = item.data('settings').placeholder_id;
                    area = $('.cms-dragarea-' + id);
                    // to calculate the correct offset, we need to set the
                    // placeholders correct heights and than set the according position
                    item.height(area.outerHeight(true));
                    // set min width
                    min = (item.width()) ? 0 : 150;
                    // as area is "css positioned" and jquery offset function is relative to the
                    // document (not the first relative/absolute parent) we need to substract
                    // first relative/absolute parent offset.
                    areaParentOffset = $(area).offsetParent().offset();
                    area.css({
                        top: item.offset().top - areaParentOffset.top - 5,
                        left: item.offset().left - areaParentOffset.left - min,
                        width: item.width() + min
                    });
                });
            },

            _drag: function () {
                var that = this;
                var originalPluginContainer;

                this.ui.sortables.nestedSortable({
                    items: '> .cms-draggable:not(.cms-draggable-disabled .cms-draggable)',
                    placeholder: 'cms-droppable',
                    connectWith: '.cms-draggables:not(.cms-hidden)',
                    tolerance: 'intersect',
                    toleranceElement: '> div',
                    dropOnEmpty: true,
                    // cloning huge structure is a performance loss compared to cloning just a dragitem
                    helper: function createHelper(e, item) {
                        var clone = item.find('> .cms-dragitem').clone();
                        clone.wrap('

');
                        return clone.parent();
                    },
                    appendTo: '.cms-structure-content',
                    // appendTo: '#cms-toolbar',
                    cursor: 'move',
                    cursorAt: { left: -15, top: -15 },
                    opacity: 1,
                    zIndex: 9999999,
                    delay: 100,
                    tabSize: 15,
                    // nestedSortable
                    listType: 'div.cms-draggables',
                    doNotClear: true,
                    disableNestingClass: 'cms-draggable-disabled',
                    errorClass: 'cms-draggable-disallowed',
                    start: function (e, ui) {
                        originalPluginContainer = ui.item.closest('.cms-draggables');
                        that.dragging = true;
                        // show empty
                        actualizeEmptyPlaceholders();
                        // ensure all menus are closed
                        CMS.Plugin._hideSettingsMenu();
                        // keep in mind that caching cms-draggables query only works
                        // as long as we don't create them on the fly
                        that.ui.sortables.each(function () {
                            var element = $(this);
                            if (element.children().length === 0) {
                                element.removeClass('cms-hidden');
                            }
                        });

                        // fixes placeholder height
                        ui.item.addClass('cms-is-dragging');
                        ui.helper.addClass('cms-draggable-is-dragging');
                        if (ui.item.find('> .cms-draggables').children().length) {
                            ui.helper.addClass('cms-draggable-stack');
                        }

                        // attach escape event to cancel dragging
                        that.ui.doc.on('keyup.cms.interrupt', function (e, cancel) {
                            if (e.keyCode === CMS.KEYS.ESC && that.dragging || cancel) {
                                that.state = false;
                                that.ui.sortables.sortable('cancel');
                            }
                        });
                    },

                    stop: function (event, ui) {
                        that.dragging = false;
                        ui.item.removeClass('cms-is-dragging cms-draggable-stack');
                        that.ui.doc.off('keyup.cms.interrupt');
                    },

                    update: function (event, ui) {
                        // cancel if isAllowed returns false
                        if (!that.state) {
                            return false;
                        }

                        var newPluginContainer = ui.item.closest('.cms-draggables');
                        if (!originalPluginContainer.is(newPluginContainer)) {
                            actualizePluginsCollapsibleStatus(newPluginContainer.add(originalPluginContainer));
                        }

                        // we pass the id to the updater which checks within the backend the correct place
                        var id = that.getId(ui.item);
                        var plugin = $('.cms-plugin-' + id);

                        // check if we copy/paste a plugin or not
                        if (plugin.closest('.cms-clipboard').length) {
                            plugin.trigger('cms.plugin.update');
                        } else {
                            plugin.trigger('cms.plugins.update');
                        }

                        // reset placeholder without entries
                        that.ui.sortables.each(function () {
                            var element = $(this);
                            if (element.children().length === 0) {
                                element.addClass('cms-hidden');
                            }
                        });

                        actualizeEmptyPlaceholders();
                    },
                    isAllowed: function (placeholder, placeholderParent, originalItem) {
                        // cancel if action is excecuted
                        if (CMS.API.locked) {
                            return false;
                        }
                        // getting restriction array
                        var bounds = [];
                        // save original state events
                        var original = $('.cms-plugin-' + that.getId(originalItem));
                        // cancel if item has no settings
                        if (original.length === 0 || original.data('settings') === null) {
                            return false;
                        }
                        var type = original.data('settings').plugin_type;
                        // prepare variables for bound
                        var holderId = that.getId(placeholder.closest('.cms-dragarea'));
                        var holder = $('.cms-placeholder-' + holderId);
                        var plugin;
                        if (placeholderParent && placeholderParent.length) {
                            // placeholderParent is always latest, it maybe that
                            // isAllowed is called _before_ placeholder is moved to a child plugin
                            plugin = $('.cms-plugin-' + that.getId(placeholderParent.closest('.cms-draggable')));
                        } else {
                            plugin = $('.cms-plugin-' + that.getId(placeholder.closest('.cms-draggable')));
                        }

                        // now set the correct bounds
                        if (holder.length) {
                            bounds = holder.data('settings').plugin_restriction;
                        }
                        if (plugin.length) {
                            bounds = plugin.data('settings').plugin_restriction;
                        }

                        // if parent has class disabled, dissalow drop
                        if (placeholder.parent().hasClass('cms-draggable-disabled')) {
                            return false;
                        }

                        // if restrictions is still empty, proceed
                        that.state = (!bounds.length || $.inArray(type, bounds) !== -1) ? true : false;

                        return that.state;
                    }
                });
            }

        });
    });
})(CMS.$);
<commit_msg>fix the case where sortable update event was misfired for a distant parent of the tree<commit_after>//##################################################################################################################
// #STRUCTUREBOARD#
/* global CMS */

(function ($) {
    'use strict';

    // CMS.$ will be passed for $
    $(function () {
        var placeholders = $('.cms-dragarea:not(.cms-clipboard-containers)');
        function actualizeEmptyPlaceholders() {
            placeholders.each(function () {
                var placeholder = $(this);
                if (placeholder
                    .find('> .cms-draggables')
                    .children('.cms-draggable:not(.cms-draggable-is-dragging)').length) {
                    placeholder.removeClass('cms-dragarea-empty');
                } else {
                    placeholder.addClass('cms-dragarea-empty');
                }
            });
        }

        function actualizePluginsCollapsibleStatus(els) {
            els.each(function () {
                var childList = $(this);
                var pluginDragItem = childList.closest('.cms-draggable').find('> .cms-dragitem');

                if (childList.children().length) {
                    pluginDragItem.addClass('cms-dragitem-collapsable cms-dragitem-expanded');
                } else {
                    pluginDragItem.removeClass('cms-dragitem-collapsable');
                }
            });
        }

        /*!
         * StructureBoard
         * handles drag & drop, mode switching and
         */
        CMS.StructureBoard = new CMS.Class({

            implement: [CMS.API.Helpers],

            options: {
                speed: 300
            },

            initialize: function (options) {
                this.options = $.extend(true, {}, this.options, options);
                this.config = CMS.config;
                this.settings = CMS.settings;

                // elements
                this._setupUI();

                // states
                this.click = 'click.cms';
                this.state = false;
                this.dragging = false;

                // setup initial stuff
                this._setup();

                // setup events
                this._events();
                actualizeEmptyPlaceholders();
            },

            _setupUI: function setupUI() {
                var container = $('.cms-structure');
                var toolbar = $('#cms-toolbar');
                this.ui = {
                    container: container,
                    content: $('.cms-structure-content'),
                    doc: $(document),
                    window: $(window),
                    toolbar: toolbar,
                    sortables: $('.cms-draggables'), // global scope to include clipboard
                    plugins: $('.cms-plugin'),
                    render_model: $('.cms-render-model'),
                    placeholders: $('.cms-placeholder'),
                    dragitems: $('.cms-draggable'),
                    dragareas: $('.cms-dragarea'),
                    clipboard: $('.cms-clipboard'),
                    toolbarModeSwitcher: toolbar.find('.cms-toolbar-item-cms-mode-switcher'),
                    toolbarModeLinks: toolbar.find('.cms-toolbar-item-cms-mode-switcher a')
                };
            },

            // initial methods
            _setup: function () {
                var that = this;
                // cancel if there are no dragareas
                if (!this.ui.dragareas.length) {
                    return false;
                }

                // cancel if there is no structure / content switcher
                if (!this.ui.toolbarModeSwitcher.length) {
                    return false;
                }

                // setup toolbar mode
                // FIXME this setTimeout is needed because
                // plugins are initialized after all the scripts are processed
                // which should be fixed btw. _resizeBoard wants plugins to be initialized,
                // otherwise throws errors
                setTimeout(function () {
                    if (that.settings.mode === 'structure') {
                        that.show(true);
                    } else {
                        // triggering hide here to switch proper classnames on switcher
                        that.hide(true);
                    }
                }, 0);

                // check if modes should be visible
                if (this.ui.placeholders.length) {
                    this.ui.toolbarModeSwitcher.show();
                }

                // add drag & drop functionality
                this._drag();
            },

            _events: function () {
                var that = this;
                var modes = that.ui.toolbarModeLinks;

                // show edit mode
                modes.eq(1).on(that.click, function (e) {
                    e.preventDefault();
                    // cancel if already active
                    if (that.settings.mode === 'edit') {
                        return false;
                    }
                    // otherwise hide
                    that.hide();
                });
                // show structure mode
                modes.eq(0).on(that.click, function (e) {
                    e.preventDefault();
                    // cancel if already active
                    if (that.settings.mode === 'structure') {
                        return false;
                    }
                    // otherwise show
                    that.show();
                });

                // keyboard handling
                // only if there is a structure / content switcher
                if (that.ui.toolbarModeSwitcher.length) {
                    that.ui.doc.on('keydown', function (e) {
                        // check if we have an important focus
                        var haveFocusedField = document.activeElement !== document.body;
                        if (e.keyCode === CMS.KEYS.SPACE && !haveFocusedField) {
                            e.preventDefault();
                            if (that.settings.mode === 'structure') {
                                that.hide();
                            } else if (that.settings.mode === 'edit') {
                                that.show();
                            }
                        }
                    });
                }
            },

            // public methods
            show: function (init) {
                // cancel show if live modus is active
                if (CMS.config.mode === 'live') {
                    return false;
                }

                // set active item
                var modes = this.ui.toolbarModeLinks;
                modes.removeClass('cms-btn-active').eq(0).addClass('cms-btn-active');

                // show clipboard
                this.ui.clipboard.fadeIn(this.options.speed);

                // apply new settings
                this.settings.mode = 'structure';
                if (!init) {
                    this.settings = this.setSettings(this.settings);
                }

                // ensure all elements are visible
                this.ui.dragareas.show();

                // show canvas
                this._showBoard();
            },

            hide: function (init) {
                // cancel show if live modus is active
                if (CMS.config.mode === 'live') {
                    return false;
                }

                // set active item
                var modes = this.ui.toolbarModeLinks;
                modes.removeClass('cms-btn-active').eq(1).addClass('cms-btn-active');

                // hide clipboard if in edit mode
                this.ui.container.find('.cms-clipboard').hide();

                // hide clipboard
                this.ui.clipboard.hide();

                this.settings.mode = 'edit';
                if (!init) {
                    this.settings = this.setSettings(this.settings);
                }

                // hide canvas
                this._hideBoard();
            },

            /**
             * gets the id of the element
             *
             * @param el jQuery element to get id from
             * @return String
             */
            getId: function (el) {
                // cancel if no element is defined
                if (el === undefined || el === null || el.length <= 0) {
                    return false;
                }

                var id = null;
                var cls = el.attr('class').split(' ')[1];

                if (el.hasClass('cms-plugin')) {
                    id = cls.replace('cms-plugin-', '');
                } else if (el.hasClass('cms-draggable')) {
                    id = cls.replace('cms-draggable-', '');
                } else if (el.hasClass('cms-placeholder')) {
                    id = cls.replace('cms-placeholder-', '');
                } else if (el.hasClass('cms-dragbar')) {
                    id = cls.replace('cms-dragbar-', '');
                } else if (el.hasClass('cms-dragarea')) {
                    id = cls.replace('cms-dragarea-', '');
                }

                return id;
            },

            getIds: function (els) {
                var that = this;
                var array = [];
                els.each(function () {
                    array.push(that.getId($(this)));
                });
                return array;
            },

            // private methods
            _showBoard: function () {
                var that = this;

                // show container
                this.ui.container.show();
                this.ui.dragareas.css('opacity', 1);

                this.ui.plugins.not(this.ui.render_model).hide();
                this.ui.placeholders.show();

                // attach event
                if (CMS.config.simpleStructureBoard) {
                    var content = this.ui.content;
                    var areas = content.find('.cms-dragarea');
                    // set correct css attributes for the new mode
                    content.addClass('cms-structure-content-simple');
                    areas.addClass('cms-dragarea-simple');
                    // lets reorder placeholders
                    areas.each(function (index, item) {
                        if ($(item).hasClass('cms-dragarea-static')) {
                            content.append(item);
                        }
                    });
                    // now lets get the first instance and add some padding
                    areas.filter('.cms-dragarea-static').eq(0).css('margin-top', '50px');
                } else {
                    this.ui.container.addClass('cms-structure-dynamic');
                    this.ui.window.on('resize.sideframe', function () {
                        that._resizeBoard();
                    }).trigger('resize.sideframe');
                }
            },

            _hideBoard: function () {
                // hide elements
                this.ui.container.hide();
                this.ui.plugins.show();
                this.ui.placeholders.hide();

                // detach event
                this.ui.window.off('resize.sideframe');

                this.ui.window.trigger('structureboard_hidden.sideframe');
                if (!CMS.config.simpleStructureBoard) {
                    this.ui.container.height(this.ui.doc.outerHeight());
                }
            },

            /*
             * @deprecated as of CMS 3.2
             */
            _resizeBoard: function () {
                // calculate placeholder position
                var id = null;
                var area = null;
                var min = null;
                var areaParentOffset = null;
                var that = this;

                // have to delay since height changes when toggling modes
                setTimeout(function () {
                    that.ui.container.height(that.ui.doc.outerHeight());
                }, 0);

                // start calculating
                this.ui.placeholders.each(function (index, item) {
                    item = $(item);
                    id = item.data('settings').placeholder_id;
                    area = $('.cms-dragarea-' + id);
                    // to calculate the correct offset, we need to set the
                    // placeholders correct heights and than set the according position
                    item.height(area.outerHeight(true));
                    // set min width
                    min = (item.width()) ? 0 : 150;
                    // as area is "css positioned" and jquery offset function is relative to the
                    // document (not the first relative/absolute parent) we need to substract
                    // first relative/absolute parent offset.
                    areaParentOffset = $(area).offsetParent().offset();
                    area.css({
                        top: item.offset().top - areaParentOffset.top - 5,
                        left: item.offset().left - areaParentOffset.left - min,
                        width: item.width() + min
                    });
                });
            },

            _drag: function () {
                var that = this;
                var originalPluginContainer;

                this.ui.sortables.nestedSortable({
                    items: '> .cms-draggable:not(.cms-draggable-disabled .cms-draggable)',
                    placeholder: 'cms-droppable',
                    connectWith: '.cms-draggables:not(.cms-hidden)',
                    tolerance: 'intersect',
                    toleranceElement: '> div',
                    dropOnEmpty: true,
                    // cloning huge structure is a performance loss compared to cloning just a dragitem
                    helper: function createHelper(e, item) {
                        var clone = item.find('> .cms-dragitem').clone();
                        clone.wrap('

');
                        return clone.parent();
                    },
                    appendTo: '.cms-structure-content',
                    // appendTo: '#cms-toolbar',
                    cursor: 'move',
                    cursorAt: { left: -15, top: -15 },
                    opacity: 1,
                    zIndex: 9999999,
                    delay: 100,
                    tabSize: 15,
                    // nestedSortable
                    listType: 'div.cms-draggables',
                    doNotClear: true,
                    disableNestingClass: 'cms-draggable-disabled',
                    errorClass: 'cms-draggable-disallowed',
                    start: function (e, ui) {
                        originalPluginContainer = ui.item.closest('.cms-draggables');
                        that.dragging = true;
                        // show empty
                        actualizeEmptyPlaceholders();
                        // ensure all menus are closed
                        CMS.Plugin._hideSettingsMenu();
                        // keep in mind that caching cms-draggables query only works
                        // as long as we don't create them on the fly
                        that.ui.sortables.each(function () {
                            var element = $(this);
                            if (element.children().length === 0) {
                                element.removeClass('cms-hidden');
                            }
                        });

                        // fixes placeholder height
                        ui.item.addClass('cms-is-dragging');
                        ui.helper.addClass('cms-draggable-is-dragging');
                        if (ui.item.find('> .cms-draggables').children().length) {
                            ui.helper.addClass('cms-draggable-stack');
                        }

                        // attach escape event to cancel dragging
                        that.ui.doc.on('keyup.cms.interrupt', function (e, cancel) {
                            if (e.keyCode === CMS.KEYS.ESC && that.dragging || cancel) {
                                that.state = false;
                                that.ui.sortables.sortable('cancel');
                            }
                        });
                    },

                    beforeStop: function (event, ui) {
                        that.dragging = false;
                        ui.item.removeClass('cms-is-dragging cms-draggable-stack');
                        that.ui.doc.off('keyup.cms.interrupt');
                    },

                    update: function (event, ui) {
                        // cancel if isAllowed returns false
                        if (!that.state) {
                            return false;
                        }

                        var newPluginContainer = ui.item.closest('.cms-draggables');
                        if (!originalPluginContainer.is(newPluginContainer)) {
                            actualizePluginsCollapsibleStatus(newPluginContainer.add(originalPluginContainer));
                        } else {
                            // if we moved inside same container,
                            // but event is fired on a parent, discard update
                            if (!newPluginContainer.is(this)) {
                                return false;
                            }
                        }

                        // we pass the id to the updater which checks within the backend the correct place
                        var id = that.getId(ui.item);
                        var plugin = $('.cms-plugin-' + id);

                        // check if we copy/paste a plugin or not
                        if (plugin.closest('.cms-clipboard').length) {
                            plugin.trigger('cms.plugin.update');
                        } else {
                            plugin.trigger('cms.plugins.update');
                        }

                        // reset placeholder without entries
                        that.ui.sortables.each(function () {
                            var element = $(this);
                            if (element.children().length === 0) {
                                element.addClass('cms-hidden');
                            }
                        });

                        actualizeEmptyPlaceholders();
                    },
                    isAllowed: function (placeholder, placeholderParent, originalItem) {
                        // cancel if action is excecuted
                        if (CMS.API.locked) {
                            return false;
                        }
                        // getting restriction array
                        var bounds = [];
                        // save original state events
                        var original = $('.cms-plugin-' + that.getId(originalItem));
                        // cancel if item has no settings
                        if (original.length === 0 || original.data('settings') === null) {
                            return false;
                        }
                        var type = original.data('settings').plugin_type;
                        // prepare variables for bound
                        var holderId = that.getId(placeholder.closest('.cms-dragarea'));
                        var holder = $('.cms-placeholder-' + holderId);
                        var plugin;
                        if (placeholderParent && placeholderParent.length) {
                            // placeholderParent is always latest, it maybe that
                            // isAllowed is called _before_ placeholder is moved to a child plugin
                            plugin = $('.cms-plugin-' + that.getId(placeholderParent.closest('.cms-draggable')));
                        } else {
                            plugin = $('.cms-plugin-' + that.getId(placeholder.closest('.cms-draggable')));
                        }

                        // now set the correct bounds
                        if (holder.length) {
                            bounds = holder.data('settings').plugin_restriction;
                        }
                        if (plugin.length) {
                            bounds = plugin.data('settings').plugin_restriction;
                        }

                        // if parent has class disabled, dissalow drop
                        if (placeholder.parent().hasClass('cms-draggable-disabled')) {
                            return false;
                        }

                        // if restrictions is still empty, proceed
                        that.state = (!bounds.length || $.inArray(type, bounds) !== -1) ? true : false;

                        return that.state;
                    }
                });
            }

        });
    });
})(CMS.$);
<|endoftext|>


<commit_before>/**
 * Script to parse and write data for a single season
 * This is supposed to be called in child processes via the main parse script
 */

const fs = require('fs')
const path = require('path')
const utils = require('./utils')
const region = require('./modules/region')
const transform = require('./modules/transform')
const stats = require('./modules/stats')
const baseline = require('./modules/baseline')
const fct = require('flusight-csv-tools')

// Variables and paths
const dataDir = './data'
const outDir = './src/assets/data'
const baselineFile = './scripts/assets/wILI_Baseline.csv'

// Get baseline data
let baselineData = baseline.getBaselines(baselineFile)

// Look for seasons in the data directory
let seasons = utils.getSubDirectories(dataDir)

// Take input file from command line argument
let seasonInFile = process.argv[2]
// Season actual files are named <season-id>-actual.json
let season = path.basename(seasonInFile).slice(0, -12)
let seasonId = parseInt(season.split('-')[0])

// Create cache for file data
let cachedCSVs = {}

let seasonOutFile
if (seasons.indexOf(season) === seasons.length - 1) {
  seasonOutFile = path.join(outDir, 'season-latest.json')
} else {
  seasonOutFile = path.join(outDir, `season-${season}.json`)
}

fs.readFile(seasonInFile, 'utf8', (err, fileData) => {
  if (err) throw err
  let actualData = JSON.parse(fileData)
  console.log(`\n Parsing data for season ${season}...`)
  let output = {
    seasonId: season, // NOTE: This id is full xxxx-yyyy type id
    regions: []
  }

  output.regions = region.regionData.map(reg => {
    // Get models for each season
    let modelsDir = utils.getSubDirectories(path.join(dataDir, season))
    let models = modelsDir.map(model => {
      // Bootstrap cache
      if (!(model in cachedCSVs)) {
        cachedCSVs[model] = {}
      }

      // Get prediction weeks for each model
      let weekStamps = utils.getWeekFiles(path.join(dataDir, season, model))
      let modelMeta = utils.getModelMeta(path.join(dataDir, season, model))
      let seasonWeekStamps = utils.seasonIdToWeekStamps(seasonId)

      let modelPredictions = seasonWeekStamps.map((sweek, index) => {
        if (weekStamps.indexOf(sweek) === -1) {
          // Prediction not available for this week, return null
          return null
        }

        let fileName = path.join(dataDir, season, model, sweek + '.csv')
        let csv = null
        // Take from cache to avoid file reads
        if (sweek in cachedCSVs[model]) {
          csv = cachedCSVs[model][sweek]
        } else {
          csv = new fct.Csv(fileName, sweek, model)
          cachedCSVs[model][sweek] = csv
        }

        return utils.regionFilterCsv(csv, reg.id, seasonWeekStamps)
      })

      return {
        id: model,
        meta: modelMeta,
        predictions: modelPredictions
      }
    })
    return {
      id: reg.id,
      actual: actualData[reg.id],
      models: models,
      baseline: baselineData[reg.subId][season]
    }
  })

  // Add model metadata
  console.log('\n Calculating model statistics')

  console.log(output)
  process.exit(1)

  region.regionData.forEach((reg, regionIndex) => {
    output.regions[regionIndex].models.forEach(model => {
      model.stats = stats.getModelStats(
        cachedCSVs[model.id],
        actualData[reg.id].map(weekData => {
          return {
            week: weekData.week,
            data: weekData.actual
          }
        }),
        reg.subId
      )
    })
  })

  // Separate distributions data from the models.
  // NOTE: This should happen somewhere above, but meh!
  utils.extractDistributions(output).forEach(regionData => {
    // If the season is latest and region is nat, save as `latest-nat`.
    // This chunk will get loaded directly with the main app.
    let outDir = './src/assets/data/distributions'
    let outputFile
    if ((seasons.indexOf(regionData.seasonId) === seasons.length - 1) && (regionData.regionId === 'nat')) {
      outputFile = path.join(outDir, 'season-latest-nat.json')
    } else {
      outputFile = path.join(outDir, `season-${regionData.seasonId}-${regionData.regionId}.json`)
    }
    fs.writeFile(outputFile, JSON.stringify(regionData), err => {
      if (err) throw err
      console.log(` Distributions file written at ${outputFile}`)
    })
  })

  fs.writeFile(seasonOutFile, JSON.stringify(utils.deleteDistributions(output)), (err) => {
    if (err) throw err
    console.log('\n ✓ .json saved at ' + seasonOutFile)
  })
})
<commit_msg>remove import for transform<commit_after>/**
 * Script to parse and write data for a single season
 * This is supposed to be called in child processes via the main parse script
 */

const fs = require('fs')
const path = require('path')
const utils = require('./utils')
const region = require('./modules/region')
const stats = require('./modules/stats')
const baseline = require('./modules/baseline')
const fct = require('flusight-csv-tools')

// Variables and paths
const dataDir = './data'
const outDir = './src/assets/data'
const baselineFile = './scripts/assets/wILI_Baseline.csv'

// Get baseline data
let baselineData = baseline.getBaselines(baselineFile)

// Look for seasons in the data directory
let seasons = utils.getSubDirectories(dataDir)

// Take input file from command line argument
let seasonInFile = process.argv[2]
// Season actual files are named <season-id>-actual.json
let season = path.basename(seasonInFile).slice(0, -12)
let seasonId = parseInt(season.split('-')[0])

// Create cache for file data
let cachedCSVs = {}

let seasonOutFile
if (seasons.indexOf(season) === seasons.length - 1) {
  seasonOutFile = path.join(outDir, 'season-latest.json')
} else {
  seasonOutFile = path.join(outDir, `season-${season}.json`)
}

fs.readFile(seasonInFile, 'utf8', (err, fileData) => {
  if (err) throw err
  let actualData = JSON.parse(fileData)
  console.log(`\n Parsing data for season ${season}...`)
  let output = {
    seasonId: season, // NOTE: This id is full xxxx-yyyy type id
    regions: []
  }

  output.regions = region.regionData.map(reg => {
    // Get models for each season
    let modelsDir = utils.getSubDirectories(path.join(dataDir, season))
    let models = modelsDir.map(model => {
      // Bootstrap cache
      if (!(model in cachedCSVs)) {
        cachedCSVs[model] = {}
      }

      // Get prediction weeks for each model
      let weekStamps = utils.getWeekFiles(path.join(dataDir, season, model))
      let modelMeta = utils.getModelMeta(path.join(dataDir, season, model))
      let seasonWeekStamps = utils.seasonIdToWeekStamps(seasonId)

      let modelPredictions = seasonWeekStamps.map((sweek, index) => {
        if (weekStamps.indexOf(sweek) === -1) {
          // Prediction not available for this week, return null
          return null
        }

        let fileName = path.join(dataDir, season, model, sweek + '.csv')
        let csv = null
        // Take from cache to avoid file reads
        if (sweek in cachedCSVs[model]) {
          csv = cachedCSVs[model][sweek]
        } else {
          csv = new fct.Csv(fileName, sweek, model)
          cachedCSVs[model][sweek] = csv
        }

        return utils.regionFilterCsv(csv, reg.id, seasonWeekStamps)
      })

      return {
        id: model,
        meta: modelMeta,
        predictions: modelPredictions
      }
    })
    return {
      id: reg.id,
      actual: actualData[reg.id],
      models: models,
      baseline: baselineData[reg.subId][season]
    }
  })

  // Add model metadata
  console.log('\n Calculating model statistics')

  console.log(output)
  process.exit(1)

  region.regionData.forEach((reg, regionIndex) => {
    output.regions[regionIndex].models.forEach(model => {
      model.stats = stats.getModelStats(
        cachedCSVs[model.id],
        actualData[reg.id].map(weekData => {
          return {
            week: weekData.week,
            data: weekData.actual
          }
        }),
        reg.subId
      )
    })
  })

  // Separate distributions data from the models.
  // NOTE: This should happen somewhere above, but meh!
  utils.extractDistributions(output).forEach(regionData => {
    // If the season is latest and region is nat, save as `latest-nat`.
    // This chunk will get loaded directly with the main app.
    let outDir = './src/assets/data/distributions'
    let outputFile
    if ((seasons.indexOf(regionData.seasonId) === seasons.length - 1) && (regionData.regionId === 'nat')) {
      outputFile = path.join(outDir, 'season-latest-nat.json')
    } else {
      outputFile = path.join(outDir, `season-${regionData.seasonId}-${regionData.regionId}.json`)
    }
    fs.writeFile(outputFile, JSON.stringify(regionData), err => {
      if (err) throw err
      console.log(` Distributions file written at ${outputFile}`)
    })
  })

  fs.writeFile(seasonOutFile, JSON.stringify(utils.deleteDistributions(output)), (err) => {
    if (err) throw err
    console.log('\n ✓ .json saved at ' + seasonOutFile)
  })
})
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import SdkConfig from './SdkConfig';

function hashCode(str) {
  var hash = 0, i, chr;
  if (str.length === 0) return hash;
  for (i = 0; i < str.length; i++) {
    chr   = str.charCodeAt(i);
    hash  = ((hash << 5) - hash) + chr;
    hash |= 0;
  }
  return Math.abs(hash);
}

export function phasedRollOutExpiredForUser(username, feature, now, rollOutConfig = SdkConfig.get().phasedRollOut) {
    if (!rollOutConfig) {
        console.log(`no phased rollout configuration, so enabling ${feature}`);
        return true;
    }
    const featureConfig = rollOutConfig[feature];
    if (!featureConfig) {
        console.log(`${feature} doesn't have phased rollout configured, so enabling`);
        return true;
    }
    if (!Number.isFinite(featureConfig.offset) || !Number.isFinite(featureConfig.period)) {
        console.error(`phased rollout of ${feature} is misconfigured, offset and/or period are not numbers, so disabling`, featureConfig);
        return false;
    }

    const hash = hashCode(username);
    //ms -> min, enable users at minute granularity
    const bucketRatio = 1000 * 60;
    const bucketCount = featureConfig.period / bucketRatio;
    const userBucket = hash % bucketCount;
    const userMs = userBucket * bucketRatio;
    const enableAt = featureConfig.offset + userMs;
    const result = now >= enableAt;
    const bucketStr = `(bucket ${userBucket}/${bucketCount})`;
    if (result) {
        console.log(`${feature} enabled for ${username} ${bucketStr}`)
    } else {
        console.log(`${feature} will be enabled for ${username} in ${Math.ceil((enableAt - now)/1000)}s ${bucketStr}`);
    }
    return result;
}
<commit_msg>this file starts in 2018<commit_after>/*
Copyright 2018 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import SdkConfig from './SdkConfig';

function hashCode(str) {
  var hash = 0, i, chr;
  if (str.length === 0) return hash;
  for (i = 0; i < str.length; i++) {
    chr   = str.charCodeAt(i);
    hash  = ((hash << 5) - hash) + chr;
    hash |= 0;
  }
  return Math.abs(hash);
}

export function phasedRollOutExpiredForUser(username, feature, now, rollOutConfig = SdkConfig.get().phasedRollOut) {
    if (!rollOutConfig) {
        console.log(`no phased rollout configuration, so enabling ${feature}`);
        return true;
    }
    const featureConfig = rollOutConfig[feature];
    if (!featureConfig) {
        console.log(`${feature} doesn't have phased rollout configured, so enabling`);
        return true;
    }
    if (!Number.isFinite(featureConfig.offset) || !Number.isFinite(featureConfig.period)) {
        console.error(`phased rollout of ${feature} is misconfigured, offset and/or period are not numbers, so disabling`, featureConfig);
        return false;
    }

    const hash = hashCode(username);
    //ms -> min, enable users at minute granularity
    const bucketRatio = 1000 * 60;
    const bucketCount = featureConfig.period / bucketRatio;
    const userBucket = hash % bucketCount;
    const userMs = userBucket * bucketRatio;
    const enableAt = featureConfig.offset + userMs;
    const result = now >= enableAt;
    const bucketStr = `(bucket ${userBucket}/${bucketCount})`;
    if (result) {
        console.log(`${feature} enabled for ${username} ${bucketStr}`)
    } else {
        console.log(`${feature} will be enabled for ${username} in ${Math.ceil((enableAt - now)/1000)}s ${bucketStr}`);
    }
    return result;
}
<|endoftext|>


<commit_before>/**
 * @file Portal component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import ReactCSSTransitionGroup from 'react-addons-transition-group';
import {createPortal} from 'react-dom';

export default class Portal extends Component {

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.wrapper = null;

        this.renderWrapper = ::this.renderWrapper;

    }

    renderWrapper() {

        if (this.wrapper) {
            return;
        }

        this.wrapper = document.createElement('div');
        this.wrapper.className = 'portal';
        document.body.appendChild(this.wrapper);

    }

    componentWillUnmount() {
        if (this.wrapper) {
            document.body.removeChild(this.wrapper);
        }
    }

    render() {

        const {className, style} = this.props;

        this.renderWrapper();

        return createPortal(
            <ReactCSSTransitionGroup component="div"
                                     className={className}
                                     style={style}>
                {this.props.children}
            </ReactCSSTransitionGroup>,
            this.wrapper
        );

    }

};

Portal.propTypes = {

    /**
     * The css class name of the root element.
     */
    className: PropTypes.string,

    /**
     * The styles of the root element.
     */
    style: PropTypes.object

};

Portal.defaultProps = {
    className: '',
    style: null
};<commit_msg>update Portal<commit_after>/**
 * @file Portal component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import ReactCSSTransitionGroup from 'react-addons-transition-group';
import {createPortal} from 'react-dom';

class Portal extends Component {

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.wrapper = null;

        this.renderWrapper = ::this.renderWrapper;

    }

    renderWrapper() {

        if (this.wrapper) {
            return;
        }

        this.wrapper = document.createElement('div');
        this.wrapper.className = 'portal';
        document.body.appendChild(this.wrapper);

    }

    componentWillUnmount() {
        if (this.wrapper) {
            document.body.removeChild(this.wrapper);
        }
    }

    render() {

        const {className, style} = this.props;

        this.renderWrapper();

        return createPortal(
            <ReactCSSTransitionGroup component="div"
                                     className={className}
                                     style={style}>
                {this.props.children}
            </ReactCSSTransitionGroup>,
            this.wrapper
        );

    }

};

Portal.propTypes = {

    /**
     * The css class name of the root element.
     */
    className: PropTypes.string,

    /**
     * The styles of the root element.
     */
    style: PropTypes.object

};

Portal.defaultProps = {
    className: '',
    style: null
};

export default Portal;<|endoftext|>


<commit_before>// creates models from pcap-ng files

// TODO code is incompehensible, make it better
const { dialog } = require('electron').remote
const fs = require('fs')
const child = require('child_process')

const commonPorts = require('./commonPorts.js')
const initialize = require('../initialize.js')
const cyOptions = require('../helpers/cyOptions.js')

// stores the node content of the file
let nodeContentJs = ''
// stores the edge content of the file
let edgeContentJs = ''

// stores the connections in the following format source, target, network protocol
let connection = []
// stores the total number device nodes
let deviceNodes = []
// stores all the connections (even duplicates src -> trg to trg -> src)
let allConnections = []
const storeConnections = txtData => {
  let srcNodes = []
  // store the target concepts
  let trgNodes = []

  txtData.map(eachLine => {
    let row = eachLine.split(' ')
    if (row[1] !== undefined && row[3] !== undefined) {
      srcNodes.push(row[1])
      trgNodes.push(row[3].replace(':', ''))
      connection.push(`${row[1]} ${row[3].replace(':', '')} ${row[4]}`)
    }
  })
  // stores the unique devices concepts based on the IPs addresses
  deviceNodes = [...new Set(srcNodes.concat(trgNodes))]
  // stores the unique transmissions between source and target devices
  allConnections = [...new Set(connection)]
}

// stores the information to create network connections
// each row format -> srcNode tgtNode protocol
let uniqueConnections = []
const removeServices = allConnections => {
  let counter = 0
  let uniqueLine = []
  // removes the services from the devices IP
  allConnections.map(row => {
    let element = row.split(' ')
    let src = element[0].split('.')
    src.pop()
    let srcIP = src.join('.')

    let trg = element[1].split('.')
    trg.pop()
    let trgIP = trg.join('.')

    // attempt to remove the duplicate connections
    // some connections have the same nodes but in opposite directions
    // src -> trg to trg -> src, this cause the srcipt to render them twice
    if (counter % 2 === 1) {
      uniqueLine.push(`${srcIP} ${trgIP} ${element[2]}`)
    } else if (counter % 2 === 0) {
      uniqueLine.push(`${trgIP} ${srcIP} ${element[2]}`)
    }
    counter += 1
  })
  // stores the unique transmissions between source and target devices
  uniqueConnections = [...new Set(uniqueLine)]
}

// to store the unique devices IP [key] and services [array]
let uniqueDevicesServices = {}
// to store the unique devices IP
let uniqueDevices = []
const storeUniqueDevicesServices = devices => {
  Object.keys(devices).map(key => {
    let nodeInformation = devices[key].split('.')
    let nodeService = nodeInformation.pop()
    let nodeIP = nodeInformation.join('.')

    if (Object.keys(uniqueDevicesServices).length === 0) {
      uniqueDevicesServices[nodeIP] = nodeService
    }
    if (uniqueDevicesServices[nodeIP] !== nodeIP) {
      uniqueDevicesServices[nodeIP] += ` ${nodeService}`
      uniqueDevices.push(nodeIP)
    }
  })
  // store the unique nodes with the device concept
  uniqueDevices = [...new Set(uniqueDevices)]
}

// unified counter to create the nodes
let idCounter = 0

const createDevices = uniqueDevicesServices => {
  Object.keys(uniqueDevicesServices).map(deviceIp => {
    nodeContentJs += `
  {
    data: {
      id: '${idCounter}',
      label: 'device',
      asto: {
        description: 'ip ${deviceIp}',
        layer: '',
        type: '',
        service: '',
        input: '',
        output: '',
        update: '',
        concept: 'device'
      }
    }
  },`
    idCounter += 1
  })
}

const createDevicesApplications = devices => {
  let deviceIdCounter = 0 // device concepts start from 0

  Object.keys(uniqueDevicesServices).map(i => {
    let services = uniqueDevicesServices[i].split(' ')
    services.map(service => {
      if (service !== 'undefined') {
        // checks if port service is known
        Object.keys(commonPorts).map(port => {
          if (port === service) {
            service = `${port} ${commonPorts[port]}`
          }
        })
        nodeContentJs += ` {
    data: {
      id: '${idCounter}',
      label: 'application',
      asto: {
        description: 'port ${service}',
        version: 'port ${service}',
        update: '',
        concept: 'application'
      }
    }
  },`

        // creates edge from the device node to the application nodes
        edgeContentJs += ` {
    data: {
      id: 'e${deviceIdCounter}${idCounter}',
      source: '${deviceIdCounter}',
      target: '${idCounter}',
      update: '',
      label: 'has'
    }
  },`
        idCounter += 1
      }
    })
    deviceIdCounter += 1
  })
}

// creates network connections and adds edges between them and the devices
const createConnections = (devices, connections) => {
  // creates the edges and the network connection nodes concept
  connections.map(row => {
    let element = row.split(' ')

    // creates the network connection nodes
    nodeContentJs += ` {
    data: {
      id: '${idCounter}',
      label: 'network connection',
      asto: {
        description: '${element[2]}',
        listOfProtocols: '${element[2]}',
        concept: 'network connection'
      }
    }
  },`

    // find the nodes id to create the edges
    let srcId = ''
    let trgId = ''
    Object.keys(devices).map(id => {
      if (devices[id] === element[0]) {
        srcId = id
      }
    })
    Object.keys(devices).map(id => {
      if (devices[id] === element[1]) {
        trgId = id
      }
    })

    // creates edges between devices and network connections
    edgeContentJs += ` {
    data: {
      id: 'e${srcId}${idCounter}',
      source: '${srcId}',
      target: '${idCounter}',
      label: 'connects'
    }
  }, {
    data: {
      id: 'e${trgId}${idCounter}',
      source: '${trgId}',
      target: '${idCounter}',
      label: 'connects'
    }
  },`
    idCounter += 1
  })
}

// writes the data from the read function
// the data are read from the txt created in readFile function
const writeGraph = (cy, filename, devices, connections) => {
  storeUniqueDevicesServices(devices)
  createDevices(uniqueDevicesServices)
  createDevicesApplications(devices)
  createConnections(uniqueDevices, connections)

  // creates the first line of the file
  const fileStart = 'const graphModel = {}\n\ngraphModel.elements = [\n// nodes'
  // end of written file
  const fileEnd = '\n]\nmodule.exports = graphModel\n'

  // concatenates the created content
  const toWrite = fileStart
    .concat(nodeContentJs)
    .concat(edgeContentJs)
    .concat(fileEnd)

  // writes the graph on file
  fs.writeFile(`${filename}.js`, toWrite, err => {
    if (err) throw err

    // loads the created graph on the tool
    cyOptions(cy, `${filename}.js`)
    initialize(cy.out, 'implementation')
  })
}

// reads the .txt file that was created by the tcpdump command
const readTxtFile = (cy, filename) => {
  fs.readFile(`${filename}`, (err, data) => {
    if (err) throw err

    const txtData = data.toString().split('\n')

    storeConnections(txtData)
    removeServices(allConnections)

    // writes graph data on as .js file
    writeGraph(cy, filename, deviceNodes, uniqueConnections)

    // deletes the text file
    fs.unlink(`${filename}`, err => {
      if (err) throw err
    })
  })
}

module.exports = function pcapImport (cy, phase) {
  // checks if tcpdump is installed, returns a boolean
  const testTcpdump = child.spawnSync('type', ['tcpdump']).status === 0

  if (testTcpdump === true) {
    let dialogOptions = []
    process.platform === 'darwin'
      ? (dialogOptions = ['openFile', 'openDirectory'])
      : (dialogOptions = ['openFile'])

    // request pcapng file to load
    dialog.showOpenDialog(
      {
        properties: [...dialogOptions],
        filters: [{ name: 'pcap', extensions: ['pcapng'] }]
      },
      pcapngFiles => {
        if (pcapngFiles === undefined) return

        const pcapngFile = pcapngFiles[0]

        // ask for save location for the new file
        dialog.showSaveDialog(filename => {
          // tcpdump command to be executed
          const tcpDumpCommand = `tcpdump -qtn -r ${pcapngFile} > ${filename}`

          child.execSync(tcpDumpCommand)

          // reads data from the txt file, creates a js file and deletes the txt
          readTxtFile(cy, filename)
        })
      }
    )
  } else {
    dialog.showErrorBox('Error', 'tcpdump not found in your path')
  }
}
<commit_msg>delete txt file at function end<commit_after>// creates models from pcap-ng files

// TODO code is incompehensible, make it better
const { dialog } = require('electron').remote
const fs = require('fs')
const child = require('child_process')

const commonPorts = require('./commonPorts.js')
const initialize = require('../initialize.js')
const cyOptions = require('../helpers/cyOptions.js')

// stores the node content of the file
let nodeContentJs = ''
// stores the edge content of the file
let edgeContentJs = ''

// stores the connections in the following format source, target, network protocol
let connection = []
// stores the total number device nodes
let deviceNodes = []
// stores all the connections (even duplicates src -> trg to trg -> src)
let allConnections = []
const storeConnections = txtData => {
  let srcNodes = []
  // store the target concepts
  let trgNodes = []

  txtData.map(eachLine => {
    let row = eachLine.split(' ')
    if (row[1] !== undefined && row[3] !== undefined) {
      srcNodes.push(row[1])
      trgNodes.push(row[3].replace(':', ''))
      connection.push(`${row[1]} ${row[3].replace(':', '')} ${row[4]}`)
    }
  })
  // stores the unique devices concepts based on the IPs addresses
  deviceNodes = [...new Set(srcNodes.concat(trgNodes))]
  // stores the unique transmissions between source and target devices
  allConnections = [...new Set(connection)]
}

// stores the information to create network connections
// each row format -> srcNode tgtNode protocol
let uniqueConnections = []
const removeServices = allConnections => {
  let counter = 0
  let uniqueLine = []
  // removes the services from the devices IP
  allConnections.map(row => {
    let element = row.split(' ')
    let src = element[0].split('.')
    src.pop()
    let srcIP = src.join('.')

    let trg = element[1].split('.')
    trg.pop()
    let trgIP = trg.join('.')

    // attempt to remove the duplicate connections
    // some connections have the same nodes but in opposite directions
    // src -> trg to trg -> src, this cause the srcipt to render them twice
    if (counter % 2 === 1) {
      uniqueLine.push(`${srcIP} ${trgIP} ${element[2]}`)
    } else if (counter % 2 === 0) {
      uniqueLine.push(`${trgIP} ${srcIP} ${element[2]}`)
    }
    counter += 1
  })
  // stores the unique transmissions between source and target devices
  uniqueConnections = [...new Set(uniqueLine)]
}

// to store the unique devices IP [key] and services [array]
let uniqueDevicesServices = {}
// to store the unique devices IP
let uniqueDevices = []
const storeUniqueDevicesServices = devices => {
  Object.keys(devices).map(key => {
    let nodeInformation = devices[key].split('.')
    let nodeService = nodeInformation.pop()
    let nodeIP = nodeInformation.join('.')

    if (Object.keys(uniqueDevicesServices).length === 0) {
      uniqueDevicesServices[nodeIP] = nodeService
    }
    if (uniqueDevicesServices[nodeIP] !== nodeIP) {
      uniqueDevicesServices[nodeIP] += ` ${nodeService}`
      uniqueDevices.push(nodeIP)
    }
  })
  // store the unique nodes with the device concept
  uniqueDevices = [...new Set(uniqueDevices)]
}

// unified counter to create the nodes
let idCounter = 0

const createDevices = uniqueDevicesServices => {
  Object.keys(uniqueDevicesServices).map(deviceIp => {
    nodeContentJs += `
  {
    data: {
      id: '${idCounter}',
      label: 'device',
      asto: {
        description: 'ip ${deviceIp}',
        layer: '',
        type: '',
        service: '',
        input: '',
        output: '',
        update: '',
        concept: 'device'
      }
    }
  },`
    idCounter += 1
  })
}

const createDevicesApplications = devices => {
  let deviceIdCounter = 0 // device concepts start from 0

  Object.keys(uniqueDevicesServices).map(i => {
    let services = uniqueDevicesServices[i].split(' ')
    services.map(service => {
      if (service !== 'undefined') {
        // checks if port service is known
        Object.keys(commonPorts).map(port => {
          if (port === service) {
            service = `${port} ${commonPorts[port]}`
          }
        })
        nodeContentJs += ` {
    data: {
      id: '${idCounter}',
      label: 'application',
      asto: {
        description: 'port ${service}',
        version: 'port ${service}',
        update: '',
        concept: 'application'
      }
    }
  },`

        // creates edge from the device node to the application nodes
        edgeContentJs += ` {
    data: {
      id: 'e${deviceIdCounter}${idCounter}',
      source: '${deviceIdCounter}',
      target: '${idCounter}',
      update: '',
      label: 'has'
    }
  },`
        idCounter += 1
      }
    })
    deviceIdCounter += 1
  })
}

// creates network connections and adds edges between them and the devices
const createConnections = (devices, connections) => {
  // creates the edges and the network connection nodes concept
  connections.map(row => {
    let element = row.split(' ')

    // creates the network connection nodes
    nodeContentJs += ` {
    data: {
      id: '${idCounter}',
      label: 'network connection',
      asto: {
        description: '${element[2]}',
        listOfProtocols: '${element[2]}',
        concept: 'network connection'
      }
    }
  },`

    // find the nodes id to create the edges
    let srcId = ''
    let trgId = ''
    Object.keys(devices).map(id => {
      if (devices[id] === element[0]) {
        srcId = id
      }
    })
    Object.keys(devices).map(id => {
      if (devices[id] === element[1]) {
        trgId = id
      }
    })

    // creates edges between devices and network connections
    edgeContentJs += ` {
    data: {
      id: 'e${srcId}${idCounter}',
      source: '${srcId}',
      target: '${idCounter}',
      label: 'connects'
    }
  }, {
    data: {
      id: 'e${trgId}${idCounter}',
      source: '${trgId}',
      target: '${idCounter}',
      label: 'connects'
    }
  },`
    idCounter += 1
  })
}

// writes the data from the read function
// the data are read from the txt created in readFile function
const writeGraph = (cy, filename, devices, connections) => {
  storeUniqueDevicesServices(devices)
  createDevices(uniqueDevicesServices)
  createDevicesApplications(devices)
  createConnections(uniqueDevices, connections)

  // creates the first line of the file
  const fileStart = 'const graphModel = {}\n\ngraphModel.elements = [\n// nodes'
  // end of written file
  const fileEnd = '\n]\nmodule.exports = graphModel\n'

  // concatenates the created content
  const toWrite = fileStart
    .concat(nodeContentJs)
    .concat(edgeContentJs)
    .concat(fileEnd)

  // writes the graph on file
  fs.writeFile(`${filename}.js`, toWrite, err => {
    if (err) throw err

    // loads the created graph on the tool
    cyOptions(cy, `${filename}.js`)
    initialize(cy.out, 'implementation')
  })
}

// reads the .txt file that was created by the tcpdump command
const readTxtFile = (cy, filename) => {
  fs.readFile(`${filename}`, (err, data) => {
    if (err) throw err

    const txtData = data.toString().split('\n')

    storeConnections(txtData)
    removeServices(allConnections)

    // writes graph data on as .js file
    writeGraph(cy, filename, deviceNodes, uniqueConnections)
  })
  // deletes the text file
  fs.unlink(`${filename}`, err => {
    if (err) throw err
  })
}

module.exports = function pcapImport (cy, phase) {
  // checks if tcpdump is installed, returns a boolean
  const testTcpdump = child.spawnSync('type', ['tcpdump']).status === 0

  if (testTcpdump === true) {
    let dialogOptions = []
    process.platform === 'darwin'
      ? (dialogOptions = ['openFile', 'openDirectory'])
      : (dialogOptions = ['openFile'])

    // request pcapng file to load
    dialog.showOpenDialog(
      {
        properties: [...dialogOptions],
        filters: [{ name: 'pcap', extensions: ['pcapng'] }]
      },
      pcapngFiles => {
        if (pcapngFiles === undefined) return

        const pcapngFile = pcapngFiles[0]

        // ask for save location for the new file
        dialog.showSaveDialog(filename => {
          // tcpdump command to be executed
          const tcpDumpCommand = `tcpdump -qtn -r ${pcapngFile} > ${filename}`

          child.execSync(tcpDumpCommand)

          // reads data from the txt file, creates a js file and deletes the txt
          readTxtFile(cy, filename)
        })
      }
    )
  } else {
    dialog.showErrorBox('Error', 'tcpdump not found in your path')
  }
}
<|endoftext|>


<commit_before>/**
 * This module provides extensions to the inbuilt String class.
 */

String.prototype.hasVowels = function() {
  /**
   * Return true if this string has vowels in it.
   */
  return /[aeiou]/.test(this);
};

String.prototype.toUpper = function() {
  /**
   * Return this String in uppercase.
   */
  return this
    .split('')
    .map(function(letter) {
      if (/[a-z]/.test(letter)) {
        return String.fromCharCode(letter.charCodeAt() - 32);
      }
      return letter;
    })
    .join('');
};

String.prototype.toLower = function() {
  /**
   * Return this String in lowercase.
   */
  return this
    .split('')
    .map(function(letter) {
      if (/[A-Z]/.test(letter)) {
        return String.fromCharCode(letter.charCodeAt() + 32);
      }
      return letter;
    })
    .join('');
};

String.prototype.ucFirst = function() {
  /**
   * Return this String with it's first letter in uppercase.
   */
  return [this.charAt(0).toUpper(), this.slice(1)].join('');
};

String.prototype.isQuestion = function() {
  /**
   * Return true if this String ends with a question mark.
   */
  return /\?$/.test(this); // or return /.*\?$/.test(this);
};

String.prototype.words = function() {
  /**
   * Return the words in this string as an Array,
   * or an empty array if the string is empty.
   */
  return this.match(/(\w+)/g) || [];
};

String.prototype.wordCount = function() {
  /**
   * Return the number of words in this String.
   */
  return this.words().length;
};

String.prototype.toCurrency = function() {
  /**
   * Return a currency representation of a String.
   * e.g 11111.11 => 11,111.11
   * Return NaN if the string is non-numerical.
   */
  var isNumberRegex = /^[0-9]+(\.[0-9]+)?$/;
  if (!isNumberRegex.test(this)) {
    return NaN;
  }

  var matches = this.split(/\./);
  var integralPart = matches[0].split('');
  var fractionalPart = matches[1] || null;

  for (var i = integralPart.length, j = 0; i > 0; i--, j++) {
    if (j && j % 3 === 0) {
      integralPart.splice(i, 0, ',');
    }
  }
  if (fractionalPart) {
    return [integralPart.join(''), fractionalPart].join('.');
  }

  return integralPart.join('');
};

String.prototype.fromCurrency = function() {
  /**
   * Return a Number representation of a currency string,
   * e.g 12,345.67 => 12345.67
   * Return NaN if the string is non-numerical.
   */
  return Number(this.replace(/,/g, ''));
};
<commit_msg>Convert the String#toCurrency method to use regex<commit_after>/**
 * This module provides extensions to the inbuilt String class.
 */

String.prototype.hasVowels = function() {
  /**
   * Return true if this string has vowels in it.
   */
  return /[aeiou]/.test(this);
};

String.prototype.toUpper = function() {
  /**
   * Return this String in uppercase.
   */
  return this
    .split('')
    .map(function(letter) {
      if (/[a-z]/.test(letter)) {
        return String.fromCharCode(letter.charCodeAt() - 32);
      }
      return letter;
    })
    .join('');
};

String.prototype.toLower = function() {
  /**
   * Return this String in lowercase.
   */
  return this
    .split('')
    .map(function(letter) {
      if (/[A-Z]/.test(letter)) {
        return String.fromCharCode(letter.charCodeAt() + 32);
      }
      return letter;
    })
    .join('');
};

String.prototype.ucFirst = function() {
  /**
   * Return this String with it's first letter in uppercase.
   */
  return [this.charAt(0).toUpper(), this.slice(1)].join('');
};

String.prototype.isQuestion = function() {
  /**
   * Return true if this String ends with a question mark.
   */
  return /\?$/.test(this); // or return /.*\?$/.test(this);
};

String.prototype.words = function() {
  /**
   * Return the words in this string as an Array,
   * or an empty array if the string is empty.
   */
  return this.match(/(\w+)/g) || [];
};

String.prototype.wordCount = function() {
  /**
   * Return the number of words in this String.
   */
  return this.words().length;
};

String.prototype.toCurrency = function() {
  /**
   * Return a currency representation of a String.
   * e.g 11111.11 => 11,111.11
   * Return NaN if the string is non-numerical.
   */
  var isNumberRegex = /^[0-9]+(\.[0-9]+)?$/;
  if (!isNumberRegex.test(this)) {
    return NaN;
  }

  return this.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
};

String.prototype.fromCurrency = function() {
  /**
   * Return a Number representation of a currency string,
   * e.g 12,345.67 => 12345.67
   * Return NaN if the string is non-numerical.
   */
  return Number(this.replace(/,/g, ''));
};
<|endoftext|>


<commit_before>// displays security suggestion depending on information found in the model
const printMsgHTML = require('../helpers/printMsgHTML.js')

// button to show the ID of the nodes
const showIDBtn = `<button id='id-button' class='menu-btn' style='color: var(--main-tx-color); background-color: var(--main-bg-color); width: 120px; height: 25px;'>show nodes ID</button>`

// contains the attribute/connection patterns and the suggestions
const list = {
  s0: {
    concept: 'device',
    layer: 'perception',
    suggestion: 'Devices in the perception layer require physical security.'
  },
  s1: {
    concept: 'device',
    update: 'false',
    suggestion: 'Treat devices that cannot be updated as compromised.'
  },
  s2: {
    concept: 'network connection',
    description: 'wireless',
    suggestion:
      'Wireless connections are subject to information disclosure attacks. Use encrypted protocols.'
  },
  s3: {
    concept: 'application',
    update: 'false',
    suggestion: 'Treat applications that cannot be updated as compromised.'
  }
}

// find nodes of interest based on the data of the list
// @node: node instance in the graph
// @concept: node concept in the suggestion array
// @graphAttribute: attribute in the graph that will be compared
// @attribute: attribute in the suggestion array
// @nodeArray: array that will store the insecure nodes
const findNodes = (node, concept, graphAttribute, attribute, nodeArray) => {
  // compare the graph nodes will the suggestions
  if (node.data().asto.concept === concept && graphAttribute === attribute) {
    // apply css rules in the graph
    node.removeClass('faded')
    node.addClass('attention')

    // push ID of the insecure nodes in the array
    nodeArray.push(node.data().id)
  }
}

// displays the nodes of interest and the suggestion
const showResults = (nodeArray, suggestion) => {
  // only show the suggestion if the node array is not empty
  if (nodeArray.length !== 0) {
    printMsgHTML(
      `Nodes: <strong>${nodeArray}</strong> <br><br>${suggestion}`
    )
  }
}

module.exports = function suggestion (cy) {
  // fade out all the nodes
  cy.elements().addClass('faded')

  // arrays to store the nodes of interest of each suggestion
  let s0nodes = []
  let s1nodes = []
  let s2nodes = []
  let s3nodes = []

  cy.nodes().map(node => {
    let nodeData = node.data().asto

    // s0 suggestion
    findNodes(node, list.s0.concept, nodeData.layer, list.s0.layer, s0nodes)
    // s1 suggestion
    findNodes(
      node,
      list.s1.concept,
      nodeData.update,
      list.s1.update,
      s1nodes
    )
    // s2 suggestion
    findNodes(node, list.s2.concept, nodeData.description, list.s2.description, s2nodes)
    // s3 suggestion
    findNodes(node, list.s3.concept, nodeData.update, list.s3.update, s3nodes)
  })

  // display the suggestions on the message area
  showResults(s0nodes, list.s0.suggestion) // s0 suggestion
  showResults(s1nodes, list.s1.suggestion) // s1 suggestion
  showResults(s2nodes, list.s2.suggestion) // s2 suggestion
  showResults(s3nodes, list.s3.suggestion) // s3 suggestion

  // render the show ID button
  printMsgHTML(showIDBtn)
  const showIdNodeBtn = document.getElementById('id-button')
  showIdNodeBtn.addEventListener('click', () => {
    cy.nodes().removeClass('label-nodes')
    cy.nodes().removeClass('label-dsc')
    cy.nodes().addClass('label-id')
  })
}
<commit_msg>added new security suggestions<commit_after>// displays security suggestion depending on information found in the model
const printMsgHTML = require('../helpers/printMsgHTML.js')

// button to show the ID of the nodes
const showIDBtn = `<button id='id-button' class='menu-btn' style='color: var(--main-tx-color); background-color: var(--main-bg-color); width: 120px; height: 25px;'>show nodes ID</button>`

// contains the attribute/connection patterns and the suggestions
const list = {
  s0: {
    concept: 'device',
    layer: 'perception',
    suggestion: 'Devices in the perception layer require physical security.'
  },
  s1: {
    concept: 'device',
    layer: 'gateway',
    suggestion: 'Devices in the gateway layer are usually external facing nodes.'
  },
  s2: {
    concept: 'device',
    layer: 'application',
    suggestion: 'Devices in the application layer usually are provided by third parties. Their security posture must taken into consideration.'
  },
  s3: {
    concept: 'device',
    update: 'false',
    suggestion: 'Treat devices that cannot be updated as compromised.'
  },
  s4: {
    concept: 'network connection',
    description: 'wireless',
    suggestion:
      'Wireless connections are subject to information disclosure attacks. Use encrypted protocols.'
  },
  s5: {
    concept: 'application',
    update: 'false',
    suggestion: 'Treat applications that cannot be updated as compromised.'
  }
}

// find nodes of interest based on the data of the list
// @node: node instance in the graph
// @concept: node concept in the suggestion array
// @graphAttribute: attribute in the graph that will be compared
// @attribute: attribute in the suggestion array
// @nodeArray: array that will store the insecure nodes
const findNodes = (node, concept, graphAttribute, attribute, nodeArray) => {
  // compare the graph nodes will the suggestions
  if (node.data().asto.concept === concept && graphAttribute === attribute) {
    // apply css rules in the graph
    node.removeClass('faded')
    node.addClass('attention')

    // push ID of the insecure nodes in the array
    nodeArray.push(node.data().id)
  }
}

// displays the nodes of interest and the suggestion
const showResults = (nodeArray, suggestion) => {
  // only show the suggestion if the node array is not empty
  if (nodeArray.length !== 0) {
    printMsgHTML(
      `Nodes: <strong>${nodeArray}</strong> <br><br>${suggestion}`
    )
  }
}

module.exports = function suggestion (cy) {
  // fade out all the nodes
  cy.elements().addClass('faded')

  // arrays to store the nodes of interest of each suggestion
  let s0nodes = []
  let s1nodes = []
  let s2nodes = []
  let s3nodes = []
  let s4nodes = []
  let s5nodes = []

  cy.nodes().map(node => {
    let nodeData = node.data().asto

    // s0 suggestion
    findNodes(node, list.s0.concept, nodeData.layer, list.s0.layer, s0nodes)
    // s1 suggestion
    findNodes(node, list.s1.concept, nodeData.layer, list.s1.layer, s1nodes)
    // s2 suggestion
    findNodes(node, list.s2.concept, nodeData.layer, list.s2.layer, s2nodes)
    // s3 suggestion
    findNodes(node, list.s3.concept, nodeData.update, list.s3.update, s3nodes)
    // s4 suggestion
    findNodes(node, list.s4.concept, nodeData.description, list.s4.description, s4nodes)
    // s5 suggestion
    findNodes(node, list.s5.concept, nodeData.update, list.s5.update, s5nodes)
  })

  // display the suggestions on the message area
  showResults(s0nodes, list.s0.suggestion) // s0 suggestion
  showResults(s1nodes, list.s1.suggestion) // s1 suggestion
  showResults(s2nodes, list.s2.suggestion) // s2 suggestion
  showResults(s3nodes, list.s3.suggestion) // s3 suggestion
  showResults(s4nodes, list.s4.suggestion) // s4 suggestion
  showResults(s5nodes, list.s5.suggestion) // s5 suggestion

  // render the show ID button
  printMsgHTML(showIDBtn)
  const showIdNodeBtn = document.getElementById('id-button')
  showIdNodeBtn.addEventListener('click', () => {
    cy.nodes().removeClass('label-nodes')
    cy.nodes().removeClass('label-dsc')
    cy.nodes().addClass('label-id')
  })
}
<|endoftext|>