diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index c80b3ccc..7b055653 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -43,7 +43,7 @@ jobs: run: ./bin/console lint:xliff translations - name: Check dependencies for security - uses: symfonycorp/security-checker-action@v2 + uses: symfonycorp/security-checker-action@v3 - name: Check doctrine mapping run: ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction diff --git a/assets/controllers/elements/collection_type_controller.js b/assets/controllers/elements/collection_type_controller.js index 4c804534..607984a2 100644 --- a/assets/controllers/elements/collection_type_controller.js +++ b/assets/controllers/elements/collection_type_controller.js @@ -103,12 +103,20 @@ export default class extends Controller { } deleteElement(event) { - bootbox.confirm(this.deleteMessageValue, (result) => { - if(result) { - const target = event.target; - //Remove the row element from the table - target.closest("tr").remove(); - } - }); + const del = () => { + const target = event.target; + //Remove the row element from the table + target.closest("tr").remove(); + } + + if(this.deleteMessageValue) { + bootbox.confirm(this.deleteMessageValue, (result) => { + if (result) { + del(); + } + }); + } else { + del(); + } } } \ No newline at end of file diff --git a/assets/controllers/elements/datatables/datatables_controller.js b/assets/controllers/elements/datatables/datatables_controller.js index 25763570..a2dc45fe 100644 --- a/assets/controllers/elements/datatables/datatables_controller.js +++ b/assets/controllers/elements/datatables/datatables_controller.js @@ -4,8 +4,8 @@ import {Controller} from "@hotwired/stimulus"; import 'datatables.net-bs5/css/dataTables.bootstrap5.css' import 'datatables.net-buttons-bs5/css/buttons.bootstrap5.css' import 'datatables.net-fixedheader-bs5/css/fixedHeader.bootstrap5.css' -import 'datatables.net-select-bs5/css/select.bootstrap5.css' import 'datatables.net-responsive-bs5/css/responsive.bootstrap5.css'; +import 'datatables.net-select-bs5/css/select.bootstrap5.css'; //JS import 'datatables.net-bs5'; @@ -23,9 +23,33 @@ export default class extends Controller { static targets = ['dt']; + static values = { + stateSaveTag: String + }; + /** The datatable instance associated with this controller instance */ _dt; + getStateSaveKey() { + let key = 'dt_state_' + + if(this.stateSaveTagValue) { //If a tag is provided, use it to store the state + key += this.stateSaveTagValue; + } else { //Otherwise generate one from the current url + key += window.location.pathname; + } + + return key; + } + + stateSaveCallback(settings, data) { + localStorage.setItem( this.getStateSaveKey(), JSON.stringify(data) ); + } + + stateLoadCallback(settings) { + return JSON.parse( localStorage.getItem(this.getStateSaveKey()) ); + } + connect() { //$($.fn.DataTable.tables()).DataTable().fixedHeader.disable(); //$($.fn.DataTable.tables()).DataTable().destroy(); @@ -39,23 +63,36 @@ export default class extends Controller { settings.url = this.element.dataset.dtUrl; + let options = { + colReorder: true, + responsive: true, + fixedHeader: { + header: $(window).width() >= 768, //Only enable fixedHeaders on devices with big screen. Fixes scrolling issues on smartphones. + headerOffset: $("#navbar").height() + }, + buttons: [{ + "extend": 'colvis', + 'className': 'mr-2 btn-light', + 'columns': ':not(.no-colvis)', + "text": "" + }], + + + rowCallback: this._rowCallback.bind(this), + stateSave: true, + stateSaveCallback: this.stateSaveCallback.bind(this), + stateLoadCallback: this.stateLoadCallback.bind(this), + }; + + if(this.isSelectable()) { + options.select = { + style: 'multi+shift', + selector: 'td.select-checkbox' + }; + } + //@ts-ignore - const promise = $(this.dtTarget).initDataTables(settings, - { - colReorder: true, - responsive: true, - fixedHeader: { - header: $(window).width() >= 768, //Only enable fixedHeaders on devices with big screen. Fixes scrolling issues on smartphones. - headerOffset: $("#navbar").height() - }, - buttons: [{ - "extend": 'colvis', - 'className': 'mr-2 btn-light', - "text": "" - }], - select: this.isSelectable(), - rowCallback: this._rowCallback.bind(this), - }) + const promise = $(this.dtTarget).initDataTables(settings, options) //Register error handler .catch(err => { console.error("Error initializing datatables: " + err); @@ -78,46 +115,6 @@ export default class extends Controller { promise.then(this._afterLoaded.bind(this)); - - //Register links. - /*promise.then(function() { - - //Set the correct title in the table. - let title = $('#part-card-header-src'); - $('#part-card-header').html(title.html()); - $(document).trigger('ajaxUI:dt_loaded'); - - - if($table.data('part_table')) { - //@ts-ignore - $('#dt').on( 'select.dt deselect.dt', function ( e, dt, items ) { - let selected_elements = dt.rows({selected: true}); - let count = selected_elements.count(); - - if(count > 0) { - $('#select_panel').removeClass('d-none'); - } else { - $('#select_panel').addClass('d-none'); - } - - $('#select_count').text(count); - - let selected_ids_string = selected_elements.data().map(function(value, index) { - return value['id']; } - ).join(","); - - $('#select_ids').val(selected_ids_string); - - } ); - } - - //Attach event listener to update links after new page selection: - $('#dt').on('draw.dt column-visibility.dt', function() { - //ajaxUI.registerLinks(); - $(document).trigger('ajaxUI:dt_loaded'); - }); - });*/ - console.debug('Datatables inited.'); } diff --git a/assets/controllers/elements/select_multiple_controller.js b/assets/controllers/elements/select_multiple_controller.js new file mode 100644 index 00000000..25291b3e --- /dev/null +++ b/assets/controllers/elements/select_multiple_controller.js @@ -0,0 +1,15 @@ +import {Controller} from "@hotwired/stimulus"; +import TomSelect from "tom-select"; + +export default class extends Controller { + _tomSelect; + + connect() { + this._tomSelect = new TomSelect(this.element, { + maxItems: 1000, + allowEmptyOption: true, + plugins: ['remove_button'], + }); + } + +} \ No newline at end of file diff --git a/assets/controllers/elements/selectpicker_controller.js b/assets/controllers/elements/selectpicker_controller.js index 182a2e35..aac08719 100644 --- a/assets/controllers/elements/selectpicker_controller.js +++ b/assets/controllers/elements/selectpicker_controller.js @@ -2,7 +2,7 @@ const bootstrap = window.bootstrap = require('bootstrap'); // without this boots require('bootstrap-select/js/bootstrap-select'); // we have to manually require the working js file import {Controller} from "@hotwired/stimulus"; -import "bootstrap-select/dist/css/bootstrap-select.css"; +import "../../css/lib/boostrap-select.css"; import "../../css/selectpicker_extensions.css"; export default class extends Controller { diff --git a/assets/controllers/elements/tagsinput_controller.js b/assets/controllers/elements/tagsinput_controller.js index 4454089f..88f1626a 100644 --- a/assets/controllers/elements/tagsinput_controller.js +++ b/assets/controllers/elements/tagsinput_controller.js @@ -8,7 +8,6 @@ export default class extends Controller { _tomSelect; connect() { - let settings = { plugins: { remove_button:{ diff --git a/assets/controllers/filters/number_constraint_controller.js b/assets/controllers/filters/number_constraint_controller.js new file mode 100644 index 00000000..2a432426 --- /dev/null +++ b/assets/controllers/filters/number_constraint_controller.js @@ -0,0 +1,26 @@ +import {Controller} from "@hotwired/stimulus"; + +export default class extends Controller { + + static targets = ["operator", "thingsToHide"]; + + connect() { + this.update(); + } + + /** + * Updates the visibility state of the value2 input, based on the operator selection. + */ + update() + { + const two_element_values = [ + "BETWEEN", + 'RANGE_IN_RANGE', + 'RANGE_INTERSECT_RANGE' + ]; + + for (const thingToHide of this.thingsToHideTargets) { + thingToHide.classList.toggle("d-none", !two_element_values.includes(this.operatorTarget.value)); + } + } +} \ No newline at end of file diff --git a/assets/controllers/helpers/form_cleanup_controller.js b/assets/controllers/helpers/form_cleanup_controller.js new file mode 100644 index 00000000..22f4fdb5 --- /dev/null +++ b/assets/controllers/helpers/form_cleanup_controller.js @@ -0,0 +1,48 @@ +import {Controller} from "@hotwired/stimulus"; + +/** + * Purpose of this controller is to clean up the form before it is finally submitted. This means empty fields get disabled, so they are not submitted. + * This is especially useful for GET forms, to prevent very long URLs + */ +export default class extends Controller { + + /** + * Call during the submit event of the form. This will disable all empty fields, so they are not submitted. + * @param event + */ + submit(event) { + /** Find the form this event belongs to */ + /** @type {HTMLFormElement} */ + const form = event.target.closest('form'); + + for(const element of form.elements) { + if(! element.value) { + element.disabled = true; + } + + //Workaround for tristate checkboxes which use a hidden field to store the value + if ((element.type === 'hidden' || element.type === 'checkbox') && element.value === 'indeterminate') { + element.disabled = true; + } + } + } + + /** + * Submits the form with all form elements disabled, so they are not submitted. This is useful for GET forms, to reset the form to not filled state. + * @param event + */ + clearAll(event) + { + const form = event.target.closest('form'); + for(const element of form.elements) { + // Do not clear elements with data-no-clear attribute + if(element.dataset.noClear) { + continue; + } + + element.disabled = true; + } + + form.submit(); + } +} \ No newline at end of file diff --git a/assets/controllers/pages/latex_preview_controller.js b/assets/controllers/pages/latex_preview_controller.js new file mode 100644 index 00000000..8c1279f1 --- /dev/null +++ b/assets/controllers/pages/latex_preview_controller.js @@ -0,0 +1,21 @@ +import {Controller} from "@hotwired/stimulus"; +import katex from "katex"; +import "katex/dist/katex.css"; + +/* stimulusFetch: 'lazy' */ +export default class extends Controller { + static targets = ["input", "preview"]; + + updatePreview() + { + katex.render(this.inputTarget.value, this.previewTarget, { + throwOnError: false, + }); + } + + connect() + { + this.updatePreview(); + this.inputTarget.addEventListener('input', this.updatePreview.bind(this)); + } +} \ No newline at end of file diff --git a/assets/controllers/pages/parameters_autocomplete_controller.js b/assets/controllers/pages/parameters_autocomplete_controller.js new file mode 100644 index 00000000..fce678ca --- /dev/null +++ b/assets/controllers/pages/parameters_autocomplete_controller.js @@ -0,0 +1,99 @@ +import {Controller} from "@hotwired/stimulus"; +import TomSelect from "tom-select"; +import katex from "katex"; +import "katex/dist/katex.css"; + +/* stimulusFetch: 'lazy' */ +export default class extends Controller +{ + static values = { + url: String, + } + + static targets = ["name", "symbol", "unit"] + + onItemAdd(value, item) { + //Retrieve the unit and symbol from the item + const symbol = item.dataset.symbol; + const unit = item.dataset.unit; + + if (this.symbolTarget && symbol !== undefined) { + this.symbolTarget.value = symbol; + //Trigger input event to update the preview + this.symbolTarget.dispatchEvent(new Event('input')); + } + if (this.unitTarget && unit !== undefined) { + this.unitTarget.value = unit; + //Trigger input event to update the preview + this.unitTarget.dispatchEvent(new Event('input')); + } + } + + connect() { + const settings = { + plugins: { + clear_button:{} + }, + persistent: false, + maxItems: 1, + //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin + delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', + createOnBlur: true, + create: true, + searchField: "name", + //labelField: "name", + valueField: "name", + onItemAdd: this.onItemAdd.bind(this), + render: { + option: (data, escape) => { + let tmp = '
' + err + ''); }); } } diff --git a/assets/js/lib/TristateCheckbox.js b/assets/js/lib/TristateCheckbox.js new file mode 100644 index 00000000..c8ceed0e --- /dev/null +++ b/assets/js/lib/TristateCheckbox.js @@ -0,0 +1,207 @@ +const DEFAULT_OPTIONS = { + true: "true", + false: "false", + null: "indeterminate", +}; + +/** + * A simple tristate checkbox + */ +export default class TristateCheckbox { + + static instances = new Map(); + + /** + * + * @type {null|boolean} + * @private + */ + _state = false; + + /** + * The element representing the checkbox. + * @type {HTMLInputElement} + * @private + */ + _element = null; + + /** + * The hidden input element representing the value of the checkbox + * @type {HTMLInputElement} + * @private + */ + _hiddenInput = null; + + /** + * The values of the checkbox. + * @type {{null: string, true: string, false: string}} + * @private + */ + _options = DEFAULT_OPTIONS; + + /** + * Retrieve the instance of the TristateCheckbox for the given element if already existing, otherwise a new one is created. + * @param element + * @param options + * @return {any} + */ + static getInstance(element, options = {}) + { + if(!TristateCheckbox.instances.has(element)) { + TristateCheckbox.instances.set(element, new TristateCheckbox(element, options)); + } + + return TristateCheckbox.instances.get(element); + } + + /** + * @param {HTMLElement} element + */ + constructor(element, options = {}) + { + if(!element instanceof HTMLInputElement || !(element.tagName === 'INPUT' && element.type === 'checkbox')) { + throw new Error("The given element is not an input checkbox"); + } + + //Apply options + this._options = Object.assign(this._options, options); + + this._element = element; + + //Set the state of our element to the value of the passed input value + this._parseInitialState(); + + //Create a hidden input field to store the value of the checkbox, because this will be always be submitted in the form + this._hiddenInput = document.createElement('input'); + this._hiddenInput.type = 'hidden'; + this._hiddenInput.name = this._element.name; + this._hiddenInput.value = this._element.value; + + //Insert the hidden input field after the checkbox and remove the checkbox from form submission (by removing the name property) + element.after(this._hiddenInput); + this._element.removeAttribute('name'); + + //Do a refresh to set the correct styling of the checkbox + this._refresh(); + + this._element.addEventListener('click', this.click.bind(this)); + } + + /** + * Parse the attributes of the checkbox and set the correct state. + * @private + */ + _parseInitialState() + { + if(this._element.hasAttribute('value')) { + this._state = this._stringToState(this._element.getAttribute('value')); + return; + } + + if(this._element.checked) { + this._state = true; + return; + } + + if(this._element.indeterminate) { + this._state = null; + return; + } + + this._state = false; + } + + _refresh() + { + this._element.indeterminate = this._state === null; + this._element.checked = this._state === true; + //Set the value field of the checkbox and the hidden input to correct value + this._element.value = this._stateToString(this._state); + this._hiddenInput.value = this._stateToString(this._state); + } + + + /** + * Returns the current state of the checkbox. True if checked, false if unchecked, null if indeterminate. + * @return {boolean|null} + */ + get state() { + return this._state; + } + + /** + * Sets the state of the checkbox. True if checked, false if unchecked, null if indeterminate. + * @param state + */ + set state(state) { + this._state = state; + this._refresh(); + } + + /** + * Returns the current state of the checkbox as string, according to the options. + * @return {string} + */ + get stateString() { + return this._stateToString(this._state); + } + + set stateString(string) { + this.state = this._stringToState(string); + this._refresh(); + } + + /** + * @param {boolean|null} state + * @return string + * @private + */ + _stateToString(state) + { + if (this.state === null) { + return this._options.null; + } else if (this.state === true) { + return this._options.true; + } else if (this.state === false) { + return this._options.false; + } + + throw new Error("Invalid state " + state); + } + + /** + * Converts a string to a state according to the options. + * @param string + * @param throwError + * @return {null|boolean} + * @private + */ + _stringToState(string, throwError = true) + { + if (string === this._options.true) { + return true; + } else if (string === this._options.false) { + return false; + } else if (string === this._options.null) { + return null; + } + + if(throwError) { + throw new Error("Invalid state string " + string); + } else { + return null; + } + } + + click() + { + switch (this._state) { + case true: this._state = false; break; + case false: this._state = null; break; + default: this._state = true; break; + } + + this._refresh(); + } + +} \ No newline at end of file diff --git a/assets/js/lib/jquery.tristate.js b/assets/js/lib/jquery.tristate.js deleted file mode 100644 index c1a85d29..00000000 --- a/assets/js/lib/jquery.tristate.js +++ /dev/null @@ -1,213 +0,0 @@ -/*jslint devel: true, bitwise: true, regexp: true, browser: true, confusion: true, unparam: true, eqeq: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */ -/*globals jQuery */ - -/*! - * Tristate v1.2.1 - * - * Copyright (c) 2013-2017 Martijn W. van der Lee - * Licensed under the MIT. - */ -/* Based on work by: - * Chris Coyier (http://css-tricks.com/indeterminate-checkboxes/) - * - * Tristate checkbox with support features - * pseudo selectors - * val() overwrite - */ - -;(function($, undefined) { - 'use strict'; - - var pluginName = 'tristate', - defaults = { - 'change': undefined, - 'checked': undefined, - 'indeterminate': undefined, - 'init': undefined, - 'reverse': false, - 'state': undefined, - 'unchecked': undefined, - 'value': undefined // one-way only! - }, - valFunction = $.fn.val; - - function Plugin(element, options) { - if($(element).is(':checkbox')) { - this.element = $(element); - this.settings = $.extend( {}, defaults, options ); - this._create(); - } - } - - $.extend(Plugin.prototype, { - _create: function() { - var that = this, - state; - - // Fix for #1 - if (window.navigator.userAgent.indexOf('Trident') >= 0) { - this.element.click(function(e) { - that._change.call(that, e); - that.element.closest('form').change(); - }); - } else { - this.element.change(function(e) { - that._change.call(that, e); - }); - } - - this.settings.checked = this.element.attr('checkedvalue') || this.settings.checked; - this.settings.unchecked = this.element.attr('uncheckedvalue') || this.settings.unchecked; - this.settings.indeterminate = this.element.attr('indeterminatevalue') || this.settings.indeterminate; - - // Initially, set state based on option state or attributes - if (typeof this.settings.state === 'undefined') { - this.settings.state = typeof this.element.attr('indeterminate') !== 'undefined'? null : this.element.is(':checked'); - } - - // If value specified, overwrite with value - if (typeof this.settings.value !== 'undefined') { - state = this._parseValue(this.settings.value); - if (typeof state !== 'undefined') { - this.settings.state = state; - } - } - - this._refresh(this.settings.init); - - return this; - }, - - _change: function(e) { - if (e.isTrigger || !e.hasOwnProperty('which')) { - e.preventDefault(); - } - - switch (this.settings.state) { - case true: this.settings.state = (this.settings.reverse ? false : null); break; - case false: this.settings.state = (this.settings.reverse ? null : true); break; - default: this.settings.state = (this.settings.reverse ? true : false); break; - } - - this._refresh(this.settings.change); - }, - - _refresh: function(callback) { - var value = this.value(); - - this.element.data("vanderlee." + pluginName, value); - - this.element[this.settings.state === null ? 'attr' : 'removeAttr']('indeterminate', 'indeterminate'); - this.element.prop('indeterminate', this.settings.state === null); - this.element.get(0).indeterminate = this.settings.state === null; - - this.element[this.settings.state === true ? 'attr' : 'removeAttr']('checked', true); - this.element.prop('checked', this.settings.state === true); - - if ($.isFunction(callback)) { - callback.call(this.element, this.settings.state, this.value()); - } - }, - - state: function(value) { - if (typeof value === 'undefined') { - return this.settings.state; - } else if (value === true || value === false || value === null) { - this.settings.state = value; - - this._refresh(this.settings.change); - } - return this; - }, - - _parseValue: function(value) { - if (value === this.settings.checked) { - return true; - } else if (value === this.settings.unchecked) { - return false; - } else if (value === this.settings.indeterminate) { - return null; - } - }, - - value: function(value) { - if (typeof value === 'undefined') { - var value; - switch (this.settings.state) { - case true: - value = this.settings.checked; - break; - - case false: - value = this.settings.unchecked; - break; - - case null: - value = this.settings.indeterminate; - break; - } - return typeof value === 'undefined'? this.element.attr('value') : value; - } else { - var state = this._parseValue(value); - if (typeof state !== 'undefined') { - this.settings.state = state; - this._refresh(this.settings.change); - } - } - } - }); - - $.fn[pluginName] = function (options, value) { - var result = this; - - this.each(function() { - if (!$.data(this, "plugin.vanderlee." + pluginName)) { - $.data(this, "plugin.vanderlee." + pluginName, new Plugin(this, options)); - } else if (typeof options === 'string') { - if (typeof value === 'undefined') { - result = $(this).data("plugin.vanderlee." + pluginName)[options](); - return false; - } else { - $(this).data("plugin.vanderlee." + pluginName)[options](value); - } - } - }); - - return result; - }; - - // Overwrite fn.val - $.fn.val = function(value) { - var data = this.data("vanderlee." + pluginName); - if (typeof data === 'undefined') { - if (typeof value === 'undefined') { - return valFunction.call(this); - } else { - return valFunction.call(this, value); - } - } else { - if (typeof value === 'undefined') { - return data; - } else { - this.data("vanderlee." + pluginName, value); - return this; - } - } - }; - - // :indeterminate pseudo selector - $.expr.filters.indeterminate = function(element) { - var $element = $(element); - return typeof $element.data("vanderlee." + pluginName) !== 'undefined' && $element.prop('indeterminate'); - }; - - // :determinate pseudo selector - $.expr.filters.determinate = function(element) { - return !($.expr.filters.indeterminate(element)); - }; - - // :tristate selector - $.expr.filters.tristate = function(element) { - return typeof $(element).data("vanderlee." + pluginName) !== 'undefined'; - }; -})(jQuery); diff --git a/assets/js/tab_remember.js b/assets/js/tab_remember.js index b0697393..78309742 100644 --- a/assets/js/tab_remember.js +++ b/assets/js/tab_remember.js @@ -55,6 +55,7 @@ class TabRememberHelper { while(parent) { //Invoker can either be a button or a element let tabInvoker = document.querySelector("button[data-content='#" + parent.id + "']") + ?? document.querySelector("button[data-bs-target='#" + parent.id + "']") ?? document.querySelector("a[href='#" + parent.id + "']"); Tab.getOrCreateInstance(tabInvoker).show(); diff --git a/assets/js/tristate_checkboxes.js b/assets/js/tristate_checkboxes.js index 5ec16b19..c59a3e22 100644 --- a/assets/js/tristate_checkboxes.js +++ b/assets/js/tristate_checkboxes.js @@ -1,49 +1,37 @@ 'use strict'; -import "./lib/jquery.tristate" +import TristateCheckbox from "./lib/TristateCheckbox"; class TristateHelper { constructor() { this.registerTriStateCheckboxes(); - this.registerSubmitHandler(); - } - - registerSubmitHandler() { - document.addEventListener("turbo:submit-start", (e) => { - var form = e.detail.formSubmission.formElement; - var formData = e.detail.formSubmission.formData; - - var $tristate_checkboxes = $('.tristate:checkbox', form); - - //Iterate over each tristate checkbox in the form and set formData to the correct value - $tristate_checkboxes.each(function() { - var $checkbox = $(this); - var state = $checkbox.tristate('state'); - - formData.set($checkbox.attr('name'), state); - }); - }); } registerTriStateCheckboxes() { //Initialize tristate checkboxes and if needed the multicheckbox functionality const listener = () => { - $(".tristate").tristate( { - checked: "true", - unchecked: "false", - indeterminate: "indeterminate", + + const tristates = document.querySelectorAll("input.tristate"); + + tristates.forEach(tristate => { + TristateCheckbox.getInstance(tristate); }); - $('.permission_multicheckbox:checkbox').change(function() { - //Find the other checkboxes in this row, and change their value - var $row = $(this).parents('tr'); - //@ts-ignore - var new_state = $(this).tristate('state'); + //Register multi checkboxes in permission tables + const multicheckboxes = document.querySelectorAll("input.permission_multicheckbox"); + multicheckboxes.forEach(multicheckbox => { + multicheckbox.addEventListener("change", (event) => { + const newValue = TristateCheckbox.getInstance(event.target).state; + const row = event.target.closest("tr"); - //@ts-ignore - $('.tristate:checkbox', $row).tristate('state', new_state); + //Find all tristate checkboxes in the same row and set their state to the new value + const tristateCheckboxes = row.querySelectorAll("input.tristate"); + tristateCheckboxes.forEach(tristateCheckbox => { + TristateCheckbox.getInstance(tristateCheckbox).state = newValue; + }); + }); }); } diff --git a/composer.lock b/composer.lock index 430d25ac..e0713e1f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "186080614c26d1b307fd99823f281e22", + "content-hash": "fae8a3a2fff35f26444bf1c3ceb771b3", "packages": [ { "name": "beberlei/assert", @@ -507,26 +507,27 @@ }, { "name": "doctrine/collections", - "version": "1.6.8", + "version": "1.7.2", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af" + "reference": "3fe77330f5591108bbf1315da7377a7e704ed8a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/1958a744696c6bb3bb0d28db2611dc11610e78af", - "reference": "1958a744696c6bb3bb0d28db2611dc11610e78af", + "url": "https://api.github.com/repos/doctrine/collections/zipball/3fe77330f5591108bbf1315da7377a7e704ed8a0", + "reference": "3fe77330f5591108bbf1315da7377a7e704ed8a0", "shasum": "" }, "require": { + "doctrine/deprecations": "^0.5.3 || ^1", "php": "^7.1.3 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^0.12", + "doctrine/coding-standard": "^9.0 || ^10.0", + "phpstan/phpstan": "^1.4.8", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", - "vimeo/psalm": "^4.2.1" + "vimeo/psalm": "^4.22" }, "type": "library", "autoload": { @@ -570,22 +571,22 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/1.6.8" + "source": "https://github.com/doctrine/collections/tree/1.7.2" }, - "time": "2021-08-10T18:51:53+00:00" + "time": "2022-08-27T16:08:58+00:00" }, { "name": "doctrine/common", - "version": "3.3.0", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "c824e95d4c83b7102d8bc60595445a6f7d540f96" + "reference": "e09556bbdf95b8420e649162b19ae9da2d1a80f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/c824e95d4c83b7102d8bc60595445a6f7d540f96", - "reference": "c824e95d4c83b7102d8bc60595445a6f7d540f96", + "url": "https://api.github.com/repos/doctrine/common/zipball/e09556bbdf95b8420e649162b19ae9da2d1a80f3", + "reference": "e09556bbdf95b8420e649162b19ae9da2d1a80f3", "shasum": "" }, "require": { @@ -594,6 +595,7 @@ }, "require-dev": { "doctrine/coding-standard": "^9.0", + "doctrine/collections": "^1", "phpstan/phpstan": "^1.4.1", "phpstan/phpstan-phpunit": "^1", "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", @@ -604,7 +606,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" + "Doctrine\\Common\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -646,7 +648,7 @@ ], "support": { "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.3.0" + "source": "https://github.com/doctrine/common/tree/3.4.0" }, "funding": [ { @@ -662,7 +664,7 @@ "type": "tidelift" } ], - "time": "2022-02-05T18:28:51+00:00" + "time": "2022-08-23T19:46:56+00:00" }, { "name": "doctrine/dbal", @@ -1110,28 +1112,28 @@ }, { "name": "doctrine/inflector", - "version": "2.0.4", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" + "reference": "ade2b3bbfb776f27f0558e26eed43b5d9fe1b392" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/ade2b3bbfb776f27f0558e26eed43b5d9fe1b392", + "reference": "ade2b3bbfb776f27f0558e26eed43b5d9fe1b392", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "vimeo/psalm": "^4.10" + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25" }, "type": "library", "autoload": { @@ -1181,7 +1183,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.4" + "source": "https://github.com/doctrine/inflector/tree/2.0.5" }, "funding": [ { @@ -1197,7 +1199,7 @@ "type": "tidelift" } ], - "time": "2021-10-22T20:16:43+00:00" + "time": "2022-09-07T09:01:28+00:00" }, { "name": "doctrine/instantiator", @@ -2877,16 +2879,16 @@ }, { "name": "masterminds/html5", - "version": "2.7.5", + "version": "2.7.6", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f640ac1bdddff06ea333a920c95bbad8872429ab" + "reference": "897eb517a343a2281f11bc5556d6548db7d93947" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f640ac1bdddff06ea333a920c95bbad8872429ab", - "reference": "f640ac1bdddff06ea333a920c95bbad8872429ab", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/897eb517a343a2281f11bc5556d6548db7d93947", + "reference": "897eb517a343a2281f11bc5556d6548db7d93947", "shasum": "" }, "require": { @@ -2940,9 +2942,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.7.5" + "source": "https://github.com/Masterminds/html5-php/tree/2.7.6" }, - "time": "2021-07-01T14:25:37+00:00" + "time": "2022-08-18T16:18:26+00:00" }, { "name": "monolog/monolog", @@ -3116,16 +3118,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.14.0", + "version": "v4.15.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", "shasum": "" }, "require": { @@ -3166,9 +3168,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" }, - "time": "2022-05-31T20:59:12+00:00" + "time": "2022-09-04T07:30:47+00:00" }, { "name": "nikolaposa/version", @@ -4014,16 +4016,16 @@ }, { "name": "php-translation/symfony-bundle", - "version": "0.12.6", + "version": "0.12.7", "source": { "type": "git", "url": "https://github.com/php-translation/symfony-bundle.git", - "reference": "1a83e47666ebebc27bbf4e57d91c7f0064056a05" + "reference": "123dfd27f5fb330d2ea5e5519565eae97f91839e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-translation/symfony-bundle/zipball/1a83e47666ebebc27bbf4e57d91c7f0064056a05", - "reference": "1a83e47666ebebc27bbf4e57d91c7f0064056a05", + "url": "https://api.github.com/repos/php-translation/symfony-bundle/zipball/123dfd27f5fb330d2ea5e5519565eae97f91839e", + "reference": "123dfd27f5fb330d2ea5e5519565eae97f91839e", "shasum": "" }, "require": { @@ -4085,9 +4087,9 @@ ], "support": { "issues": "https://github.com/php-translation/symfony-bundle/issues", - "source": "https://github.com/php-translation/symfony-bundle/tree/0.12.6" + "source": "https://github.com/php-translation/symfony-bundle/tree/0.12.7" }, - "time": "2022-04-06T11:17:06+00:00" + "time": "2022-09-02T15:33:16+00:00" }, { "name": "php-translation/symfony-storage", @@ -4914,16 +4916,16 @@ }, { "name": "s9e/text-formatter", - "version": "2.11.3", + "version": "2.11.4", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "e94e1823714da475a9df50c5319e29c2bb91c591" + "reference": "a7f31582f97abe17ae6b7c95a198dc0d48e7d1a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/e94e1823714da475a9df50c5319e29c2bb91c591", - "reference": "e94e1823714da475a9df50c5319e29c2bb91c591", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/a7f31582f97abe17ae6b7c95a198dc0d48e7d1a1", + "reference": "a7f31582f97abe17ae6b7c95a198dc0d48e7d1a1", "shasum": "" }, "require": { @@ -4950,7 +4952,7 @@ }, "type": "library", "extra": { - "version": "2.11.3" + "version": "2.11.4" }, "autoload": { "psr-4": { @@ -4982,9 +4984,9 @@ ], "support": { "issues": "https://github.com/s9e/TextFormatter/issues", - "source": "https://github.com/s9e/TextFormatter/tree/2.11.3" + "source": "https://github.com/s9e/TextFormatter/tree/2.11.4" }, - "time": "2022-06-13T00:31:16+00:00" + "time": "2022-08-17T21:44:18+00:00" }, { "name": "sabberworm/php-css-parser", @@ -5256,16 +5258,16 @@ }, { "name": "sensio/framework-extra-bundle", - "version": "v6.2.6", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git", - "reference": "6bd976c99ef3f78e31c9490a10ba6dd8901076eb" + "reference": "bb962f8aed09e60b0942545f6e4842ffeee4aafd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/6bd976c99ef3f78e31c9490a10ba6dd8901076eb", - "reference": "6bd976c99ef3f78e31c9490a10ba6dd8901076eb", + "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/bb962f8aed09e60b0942545f6e4842ffeee4aafd", + "reference": "bb962f8aed09e60b0942545f6e4842ffeee4aafd", "shasum": "" }, "require": { @@ -5328,9 +5330,9 @@ ], "support": { "issues": "https://github.com/sensiolabs/SensioFrameworkExtraBundle/issues", - "source": "https://github.com/sensiolabs/SensioFrameworkExtraBundle/tree/v6.2.6" + "source": "https://github.com/sensiolabs/SensioFrameworkExtraBundle/tree/v6.2.8" }, - "time": "2022-01-14T11:51:13+00:00" + "time": "2022-09-05T16:44:56+00:00" }, { "name": "shivas/versioning-bundle", @@ -5824,16 +5826,16 @@ }, { "name": "symfony/console", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "535846c7ee6bc4dd027ca0d93220601456734b10" + "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10", - "reference": "535846c7ee6bc4dd027ca0d93220601456734b10", + "url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1", + "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1", "shasum": "" }, "require": { @@ -5903,7 +5905,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.11" + "source": "https://github.com/symfony/console/tree/v5.4.12" }, "funding": [ { @@ -5919,7 +5921,7 @@ "type": "tidelift" } ], - "time": "2022-07-22T10:42:43+00:00" + "time": "2022-08-17T13:18:05+00:00" }, { "name": "symfony/css-selector", @@ -6631,16 +6633,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd" + "reference": "2d67c1f9a1937406a9be3171b4b22250c0a11447" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/6699fb0228d1bc35b12aed6dd5e7455457609ddd", - "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2d67c1f9a1937406a9be3171b4b22250c0a11447", + "reference": "2d67c1f9a1937406a9be3171b4b22250c0a11447", "shasum": "" }, "require": { @@ -6675,7 +6677,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.11" + "source": "https://github.com/symfony/filesystem/tree/v5.4.12" }, "funding": [ { @@ -6691,7 +6693,7 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2022-08-02T13:48:16+00:00" }, { "name": "symfony/finder", @@ -6823,16 +6825,16 @@ }, { "name": "symfony/form", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "1c156d7093cce68600604f155cb51065e897d7fa" + "reference": "d8c5cc929f8dc7a58b710c9474dd7a0173006017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/1c156d7093cce68600604f155cb51065e897d7fa", - "reference": "1c156d7093cce68600604f155cb51065e897d7fa", + "url": "https://api.github.com/repos/symfony/form/zipball/d8c5cc929f8dc7a58b710c9474dd7a0173006017", + "reference": "d8c5cc929f8dc7a58b710c9474dd7a0173006017", "shasum": "" }, "require": { @@ -6906,7 +6908,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v5.4.11" + "source": "https://github.com/symfony/form/tree/v5.4.12" }, "funding": [ { @@ -6922,20 +6924,20 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2022-08-05T13:13:10+00:00" }, { "name": "symfony/framework-bundle", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "a0660b602357d5c2ceaac1c9f80c5820bbff803d" + "reference": "49f8fe5d39b7513a3f26898788885dbe66b0d910" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a0660b602357d5c2ceaac1c9f80c5820bbff803d", - "reference": "a0660b602357d5c2ceaac1c9f80c5820bbff803d", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/49f8fe5d39b7513a3f26898788885dbe66b0d910", + "reference": "49f8fe5d39b7513a3f26898788885dbe66b0d910", "shasum": "" }, "require": { @@ -7057,7 +7059,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v5.4.11" + "source": "https://github.com/symfony/framework-bundle/tree/v5.4.12" }, "funding": [ { @@ -7073,20 +7075,20 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2022-08-26T10:32:10+00:00" }, { "name": "symfony/http-client", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "5c5c37eb2a276d8d7d669dd76688aa1606ee78fb" + "reference": "6a057be154824487fd5e6b65ab83899e0c5ac550" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/5c5c37eb2a276d8d7d669dd76688aa1606ee78fb", - "reference": "5c5c37eb2a276d8d7d669dd76688aa1606ee78fb", + "url": "https://api.github.com/repos/symfony/http-client/zipball/6a057be154824487fd5e6b65ab83899e0c5ac550", + "reference": "6a057be154824487fd5e6b65ab83899e0c5ac550", "shasum": "" }, "require": { @@ -7144,7 +7146,7 @@ "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-client/tree/v5.4.11" + "source": "https://github.com/symfony/http-client/tree/v5.4.12" }, "funding": [ { @@ -7160,7 +7162,7 @@ "type": "tidelift" } ], - "time": "2022-07-28T13:33:28+00:00" + "time": "2022-08-02T15:52:22+00:00" }, { "name": "symfony/http-client-contracts", @@ -7242,16 +7244,16 @@ }, { "name": "symfony/http-foundation", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389" + "reference": "f4bfe9611b113b15d98a43da68ec9b5a00d56791" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/0a5868e0999e9d47859ba3d918548ff6943e6389", - "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f4bfe9611b113b15d98a43da68ec9b5a00d56791", + "reference": "f4bfe9611b113b15d98a43da68ec9b5a00d56791", "shasum": "" }, "require": { @@ -7263,8 +7265,11 @@ "require-dev": { "predis/predis": "~1.0", "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0" + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" }, "suggest": { "symfony/mime": "To use the file extension guesser" @@ -7295,7 +7300,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.11" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.12" }, "funding": [ { @@ -7311,20 +7316,20 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2022-08-19T07:33:17+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee" + "reference": "37f660fa3bcd78fe4893ce23ebe934618ec099be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4fd590a2ef3f62560dbbf6cea511995dd77321ee", - "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/37f660fa3bcd78fe4893ce23ebe934618ec099be", + "reference": "37f660fa3bcd78fe4893ce23ebe934618ec099be", "shasum": "" }, "require": { @@ -7407,7 +7412,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.11" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.12" }, "funding": [ { @@ -7423,7 +7428,7 @@ "type": "tidelift" } ], - "time": "2022-07-29T12:30:22+00:00" + "time": "2022-08-26T14:40:40+00:00" }, { "name": "symfony/intl", @@ -7515,16 +7520,16 @@ }, { "name": "symfony/mailer", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "9f34f71ec05cef8a0d434988476ee9fd32075a6c" + "reference": "076043af11e58b20a68d2fd93f59cdbc6e8fdd00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/9f34f71ec05cef8a0d434988476ee9fd32075a6c", - "reference": "9f34f71ec05cef8a0d434988476ee9fd32075a6c", + "url": "https://api.github.com/repos/symfony/mailer/zipball/076043af11e58b20a68d2fd93f59cdbc6e8fdd00", + "reference": "076043af11e58b20a68d2fd93f59cdbc6e8fdd00", "shasum": "" }, "require": { @@ -7571,7 +7576,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v5.4.11" + "source": "https://github.com/symfony/mailer/tree/v5.4.12" }, "funding": [ { @@ -7587,20 +7592,20 @@ "type": "tidelift" } ], - "time": "2022-07-24T16:05:20+00:00" + "time": "2022-08-03T05:17:26+00:00" }, { "name": "symfony/mime", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "3cd175cdcdb6db2e589e837dd46aff41027d9830" + "reference": "03876e9c5a36f5b45e7d9a381edda5421eff8a90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/3cd175cdcdb6db2e589e837dd46aff41027d9830", - "reference": "3cd175cdcdb6db2e589e837dd46aff41027d9830", + "url": "https://api.github.com/repos/symfony/mime/zipball/03876e9c5a36f5b45e7d9a381edda5421eff8a90", + "reference": "03876e9c5a36f5b45e7d9a381edda5421eff8a90", "shasum": "" }, "require": { @@ -7654,7 +7659,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.11" + "source": "https://github.com/symfony/mime/tree/v5.4.12" }, "funding": [ { @@ -7670,7 +7675,7 @@ "type": "tidelift" } ], - "time": "2022-07-20T11:34:24+00:00" + "time": "2022-08-19T14:24:03+00:00" }, { "name": "symfony/monolog-bridge", @@ -9604,16 +9609,16 @@ }, { "name": "symfony/security-http", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "447f8b5313f17b6a1297df6a9d0fc36fb555de4d" + "reference": "3ca3eb2a866a4a5adaf0a952d2d7db7208da378b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/447f8b5313f17b6a1297df6a9d0fc36fb555de4d", - "reference": "447f8b5313f17b6a1297df6a9d0fc36fb555de4d", + "url": "https://api.github.com/repos/symfony/security-http/zipball/3ca3eb2a866a4a5adaf0a952d2d7db7208da378b", + "reference": "3ca3eb2a866a4a5adaf0a952d2d7db7208da378b", "shasum": "" }, "require": { @@ -9669,7 +9674,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v5.4.11" + "source": "https://github.com/symfony/security-http/tree/v5.4.12" }, "funding": [ { @@ -9685,20 +9690,20 @@ "type": "tidelift" } ], - "time": "2022-07-29T07:37:50+00:00" + "time": "2022-08-23T10:55:18+00:00" }, { "name": "symfony/serializer", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "412e2a242a380267f3ddf281047b8720d2ad9b08" + "reference": "776fa3010f62b97a7119757a66596a654cd244d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/412e2a242a380267f3ddf281047b8720d2ad9b08", - "reference": "412e2a242a380267f3ddf281047b8720d2ad9b08", + "url": "https://api.github.com/repos/symfony/serializer/zipball/776fa3010f62b97a7119757a66596a654cd244d4", + "reference": "776fa3010f62b97a7119757a66596a654cd244d4", "shasum": "" }, "require": { @@ -9772,7 +9777,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v5.4.11" + "source": "https://github.com/symfony/serializer/tree/v5.4.12" }, "funding": [ { @@ -9788,7 +9793,7 @@ "type": "tidelift" } ], - "time": "2022-07-28T13:33:28+00:00" + "time": "2022-08-26T10:32:10+00:00" }, { "name": "symfony/service-contracts", @@ -9937,16 +9942,16 @@ }, { "name": "symfony/string", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322" + "reference": "2fc515e512d721bf31ea76bd02fe23ada4640058" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/5eb661e49ad389e4ae2b6e4df8d783a8a6548322", - "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "url": "https://api.github.com/repos/symfony/string/zipball/2fc515e512d721bf31ea76bd02fe23ada4640058", + "reference": "2fc515e512d721bf31ea76bd02fe23ada4640058", "shasum": "" }, "require": { @@ -10003,7 +10008,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.11" + "source": "https://github.com/symfony/string/tree/v5.4.12" }, "funding": [ { @@ -10019,7 +10024,7 @@ "type": "tidelift" } ], - "time": "2022-07-24T16:15:25+00:00" + "time": "2022-08-12T17:03:11+00:00" }, { "name": "symfony/templating", @@ -10091,16 +10096,16 @@ }, { "name": "symfony/translation", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21" + "reference": "42ecc77eb4f229ce2df702a648ec93b8478d76ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21", - "reference": "7a1a8f6bbff269f434a83343a0a5d36a4f8cfa21", + "url": "https://api.github.com/repos/symfony/translation/zipball/42ecc77eb4f229ce2df702a648ec93b8478d76ae", + "reference": "42ecc77eb4f229ce2df702a648ec93b8478d76ae", "shasum": "" }, "require": { @@ -10168,7 +10173,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.4.11" + "source": "https://github.com/symfony/translation/tree/v5.4.12" }, "funding": [ { @@ -10184,7 +10189,7 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2022-08-02T15:52:22+00:00" }, { "name": "symfony/translation-contracts", @@ -10266,16 +10271,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "63b8a50d48c9fe3d04e77307d4f1771dd848baa8" + "reference": "94c3b38514c953e3e84719c96d4e578a01ca1819" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/63b8a50d48c9fe3d04e77307d4f1771dd848baa8", - "reference": "63b8a50d48c9fe3d04e77307d4f1771dd848baa8", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/94c3b38514c953e3e84719c96d4e578a01ca1819", + "reference": "94c3b38514c953e3e84719c96d4e578a01ca1819", "shasum": "" }, "require": { @@ -10367,7 +10372,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v5.4.11" + "source": "https://github.com/symfony/twig-bridge/tree/v5.4.12" }, "funding": [ { @@ -10383,7 +10388,7 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2022-08-03T13:09:21+00:00" }, { "name": "symfony/twig-bundle", @@ -10476,7 +10481,7 @@ }, { "name": "symfony/ux-turbo", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-turbo.git", @@ -10554,7 +10559,7 @@ "turbo-stream" ], "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.3.0" + "source": "https://github.com/symfony/ux-turbo/tree/v2.4.0" }, "funding": [ { @@ -10574,16 +10579,16 @@ }, { "name": "symfony/validator", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "d6457034ba8a4ea6703e5607829a337b66a53ce8" + "reference": "38bc4d83d01b800f1fa5acaceb5ff77490b8f768" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/d6457034ba8a4ea6703e5607829a337b66a53ce8", - "reference": "d6457034ba8a4ea6703e5607829a337b66a53ce8", + "url": "https://api.github.com/repos/symfony/validator/zipball/38bc4d83d01b800f1fa5acaceb5ff77490b8f768", + "reference": "38bc4d83d01b800f1fa5acaceb5ff77490b8f768", "shasum": "" }, "require": { @@ -10667,7 +10672,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v5.4.11" + "source": "https://github.com/symfony/validator/tree/v5.4.12" }, "funding": [ { @@ -10683,7 +10688,7 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2022-08-09T11:54:29+00:00" }, { "name": "symfony/var-dumper", @@ -11009,16 +11014,16 @@ }, { "name": "symfony/yaml", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e" + "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/05d4ea560f3402c6c116afd99fdc66e60eda227e", - "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e", + "url": "https://api.github.com/repos/symfony/yaml/zipball/7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c", + "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c", "shasum": "" }, "require": { @@ -11064,7 +11069,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.11" + "source": "https://github.com/symfony/yaml/tree/v5.4.12" }, "funding": [ { @@ -11080,7 +11085,7 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2022-08-02T15:52:22+00:00" }, { "name": "tecnickcom/tc-lib-barcode", @@ -13085,16 +13090,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.8.2", + "version": "1.8.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "c53312ecc575caf07b0e90dee43883fdf90ca67c" + "reference": "f6598a5ff12ca4499a836815e08b4d77a2ddeb20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c53312ecc575caf07b0e90dee43883fdf90ca67c", - "reference": "c53312ecc575caf07b0e90dee43883fdf90ca67c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f6598a5ff12ca4499a836815e08b4d77a2ddeb20", + "reference": "f6598a5ff12ca4499a836815e08b4d77a2ddeb20", "shasum": "" }, "require": { @@ -13118,9 +13123,13 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.2" + "source": "https://github.com/phpstan/phpstan/tree/1.8.5" }, "funding": [ { @@ -13131,34 +13140,30 @@ "url": "https://github.com/phpstan", "type": "github" }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, { "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", "type": "tidelift" } ], - "time": "2022-07-20T09:57:31+00:00" + "time": "2022-09-07T16:05:32+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "1.3.12", + "version": "1.3.13", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "c5ec462889f3bcee32be57ff26f775295836c173" + "reference": "62a3b4252d502f0ead9c145055947b38b8568498" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/c5ec462889f3bcee32be57ff26f775295836c173", - "reference": "c5ec462889f3bcee32be57ff26f775295836c173", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/62a3b4252d502f0ead9c145055947b38b8568498", + "reference": "62a3b4252d502f0ead9c145055947b38b8568498", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.8.0" + "phpstan/phpstan": "^1.8.3" }, "conflict": { "doctrine/collections": "<1.0", @@ -13176,6 +13181,7 @@ "doctrine/mongodb-odm": "^1.3 || ^2.1", "doctrine/orm": "^2.11.0", "doctrine/persistence": "^1.3.8 || ^2.2.1", + "gedmo/doctrine-extensions": "^3.8", "nesbot/carbon": "^2.49", "nikic/php-parser": "^4.13.2", "php-parallel-lint/php-parallel-lint": "^1.2", @@ -13206,22 +13212,22 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.3.12" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.3.13" }, - "time": "2022-08-08T18:06:58+00:00" + "time": "2022-09-06T14:54:00+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "1.2.9", + "version": "1.2.13", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "f4cb3b8915d3656e780f305f01c86b70ff933272" + "reference": "016e441a19a2af79ca0c60920ba0d61747b4e855" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/f4cb3b8915d3656e780f305f01c86b70ff933272", - "reference": "f4cb3b8915d3656e780f305f01c86b70ff933272", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/016e441a19a2af79ca0c60920ba0d61747b4e855", + "reference": "016e441a19a2af79ca0c60920ba0d61747b4e855", "shasum": "" }, "require": { @@ -13239,15 +13245,15 @@ "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5", "psr/container": "1.0 || 1.1.1", - "symfony/config": "^4.2 || ^5.0", - "symfony/console": "^4.0 || ^5.0", - "symfony/dependency-injection": "^4.0 || ^5.0", - "symfony/form": "^4.0 || ^5.0", - "symfony/framework-bundle": "^4.4 || ^5.0", - "symfony/http-foundation": "^5.1", - "symfony/messenger": "^4.2 || ^5.0", + "symfony/config": "^5.4 || ^6.1", + "symfony/console": "^5.4 || ^6.1", + "symfony/dependency-injection": "^5.4 || ^6.1", + "symfony/form": "^5.4 || ^6.1", + "symfony/framework-bundle": "^5.4 || ^6.1", + "symfony/http-foundation": "^5.4 || ^6.1", + "symfony/messenger": "^5.4", "symfony/polyfill-php80": "^1.24", - "symfony/serializer": "^4.0 || ^5.0" + "symfony/serializer": "^5.4" }, "type": "phpstan-extension", "extra": { @@ -13277,9 +13283,9 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/1.2.9" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.2.13" }, - "time": "2022-08-05T20:13:38+00:00" + "time": "2022-08-28T13:34:45+00:00" }, { "name": "psalm/plugin-symfony", @@ -13348,22 +13354,23 @@ }, { "name": "roave/security-advisories", - "version": "dev-master", + "version": "dev-latest", "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "773292d413a97c357a0b49635afd5fdb1d4f314a" + "reference": "6d260392fad173d6ee6e3a93c875d9327db1109b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/773292d413a97c357a0b49635afd5fdb1d4f314a", - "reference": "773292d413a97c357a0b49635afd5fdb1d4f314a", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/6d260392fad173d6ee6e3a93c875d9327db1109b", + "reference": "6d260392fad173d6ee6e3a93c875d9327db1109b", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", "admidio/admidio": "<4.1.9", "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", + "aheinze/cockpit": "<=2.2.1", "akaunting/akaunting": "<2.1.13", "alextselegidis/easyappointments": "<=1.4.3", "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", @@ -13407,6 +13414,7 @@ "codeception/codeception": "<3.1.3|>=4,<4.1.22", "codeigniter/framework": "<=3.0.6", "codeigniter4/framework": "<4.1.9", + "codeigniter4/shield": "= 1.0.0-beta", "codiad/codiad": "<=2.8.4", "composer/composer": "<1.10.26|>=2-alpha.1,<2.2.12|>=2.3,<2.3.5", "concrete5/concrete5": "<9", @@ -13491,8 +13499,9 @@ "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", "getgrav/grav": "<1.7.34", - "getkirby/cms": "<3.5.8", + "getkirby/cms": "<3.5.8.1|>=3.6,<3.6.6.1|>=3.7,<3.7.4", "getkirby/panel": "<2.5.14", + "getkirby/starterkit": "<=3.7.0.2", "gilacms/gila": "<=1.11.4", "globalpayments/php-sdk": "<2", "google/protobuf": "<3.15", @@ -13543,7 +13552,6 @@ "laminas/laminas-http": "<2.14.2", "laravel/fortify": "<1.11.1", "laravel/framework": "<6.20.42|>=7,<7.30.6|>=8,<8.75", - "laravel/laravel": "<=9.1.8", "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", "latte/latte": "<2.10.8", "lavalite/cms": "<=5.8", @@ -13586,6 +13594,7 @@ "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", "nilsteampassnet/teampass": "<=2.1.27.36", + "notrinos/notrinos-erp": "<=0.7", "noumo/easyii": "<=0.9", "nukeviet/nukeviet": "<4.5.2", "nystudio107/craft-seomatic": "<3.4.12", @@ -13631,14 +13640,14 @@ "pimcore/data-hub": "<1.2.4", "pimcore/pimcore": "<10.4.4", "pocketmine/bedrock-protocol": "<8.0.2", - "pocketmine/pocketmine-mp": ">= 4.0.0-BETA5, < 4.4.2|<4.2.10", + "pocketmine/pocketmine-mp": "<4.7.2|>= 4.0.0-BETA5, < 4.4.2", "pressbooks/pressbooks": "<5.18", "prestashop/autoupgrade": ">=4,<4.10.1", "prestashop/blockwishlist": ">=2,<2.1.1", "prestashop/contactform": ">1.0.1,<4.3", "prestashop/gamification": "<2.3.2", "prestashop/prestashop": ">=1.6.0.10,<1.7.8.7", - "prestashop/productcomments": ">=4,<4.2.1", + "prestashop/productcomments": "<5.0.2", "prestashop/ps_emailsubscription": "<2.6.1", "prestashop/ps_facetedsearch": "<3.4.1", "prestashop/ps_linklist": "<3.1", @@ -13689,7 +13698,7 @@ "simplito/elliptic-php": "<1.0.6", "slim/slim": "<2.6", "smarty/smarty": "<3.1.45|>=4,<4.1.1", - "snipe/snipe-it": "<=6.0.2|>= 6.0.0-RC-1, <= 6.0.0-RC-5", + "snipe/snipe-it": "<6.0.10|>= 6.0.0-RC-1, <= 6.0.0-RC-5", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", "spipu/html2pdf": "<5.2.4", @@ -13827,6 +13836,7 @@ "zfr/zfr-oauth2-server-module": "<0.1.2", "zoujingli/thinkadmin": "<6.0.22" }, + "default-branch": true, "type": "metapackage", "notification-url": "https://packagist.org/downloads/", "license": [ @@ -13859,7 +13869,7 @@ "type": "tidelift" } ], - "time": "2022-08-12T16:04:45+00:00" + "time": "2022-08-31T22:04:18+00:00" }, { "name": "sebastian/diff", @@ -14080,16 +14090,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "0b900ca5576ecd59e08c76127e616667cfe427a7" + "reference": "291c1e92281a09152dda089f782e23dedd34bd4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0b900ca5576ecd59e08c76127e616667cfe427a7", - "reference": "0b900ca5576ecd59e08c76127e616667cfe427a7", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/291c1e92281a09152dda089f782e23dedd34bd4f", + "reference": "291c1e92281a09152dda089f782e23dedd34bd4f", "shasum": "" }, "require": { @@ -14135,7 +14145,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.11" + "source": "https://github.com/symfony/dom-crawler/tree/v5.4.12" }, "funding": [ { @@ -14151,7 +14161,7 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2022-08-03T13:09:21+00:00" }, { "name": "symfony/maker-bundle", @@ -14409,16 +14419,16 @@ }, { "name": "symplify/easy-coding-standard", - "version": "11.1.4", + "version": "11.1.9", "source": { "type": "git", "url": "https://github.com/symplify/easy-coding-standard.git", - "reference": "d70ff73140ef96b1faa04c93fc57b2b1e9d6d8bd" + "reference": "65341819f0f518b5f424a57736e9b0e9c5a9202f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/d70ff73140ef96b1faa04c93fc57b2b1e9d6d8bd", - "reference": "d70ff73140ef96b1faa04c93fc57b2b1e9d6d8bd", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/65341819f0f518b5f424a57736e9b0e9c5a9202f", + "reference": "65341819f0f518b5f424a57736e9b0e9c5a9202f", "shasum": "" }, "require": { @@ -14448,7 +14458,7 @@ ], "description": "Prefixed scoped version of ECS package", "support": { - "source": "https://github.com/symplify/easy-coding-standard/tree/11.1.4" + "source": "https://github.com/symplify/easy-coding-standard/tree/11.1.9" }, "funding": [ { @@ -14460,20 +14470,20 @@ "type": "github" } ], - "time": "2022-08-13T19:37:11+00:00" + "time": "2022-09-02T10:10:26+00:00" }, { "name": "vimeo/psalm", - "version": "4.26.0", + "version": "4.27.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "6998fabb2bf528b65777bf9941920888d23c03ac" + "reference": "faf106e717c37b8c81721845dba9de3d8deed8ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/6998fabb2bf528b65777bf9941920888d23c03ac", - "reference": "6998fabb2bf528b65777bf9941920888d23c03ac", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/faf106e717c37b8c81721845dba9de3d8deed8ff", + "reference": "faf106e717c37b8c81721845dba9de3d8deed8ff", "shasum": "" }, "require": { @@ -14565,9 +14575,9 @@ ], "support": { "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.26.0" + "source": "https://github.com/vimeo/psalm/tree/4.27.0" }, - "time": "2022-07-31T13:10:26+00:00" + "time": "2022-08-31T13:47:09+00:00" }, { "name": "webmozart/path-util", diff --git a/config/packages/datatables.yaml b/config/packages/datatables.yaml index 18fbb18d..7f85df10 100644 --- a/config/packages/datatables.yaml +++ b/config/packages/datatables.yaml @@ -3,6 +3,9 @@ datatables: language_from_cdn: false + # Set to none, as we override the bundle mechanism with our own custom one + persist_state: none + # Set options, as documented at https://datatables.net/reference/option/ options: lengthMenu : [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]] diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 8f9d1586..44ea77f3 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -32,4 +32,5 @@ doctrine: dql: string_functions: - regexp: DoctrineExtensions\Query\Mysql\Regexp \ No newline at end of file + regexp: DoctrineExtensions\Query\Mysql\Regexp + ifnull: DoctrineExtensions\Query\Mysql\IfNull \ No newline at end of file diff --git a/config/packages/nelmio_security.yaml b/config/packages/nelmio_security.yaml index 24e0a50d..d97b3983 100644 --- a/config/packages/nelmio_security.yaml +++ b/config/packages/nelmio_security.yaml @@ -58,3 +58,9 @@ nelmio_security: - 'data:' block-all-mixed-content: true # defaults to false, blocks HTTP content over HTTPS transport # upgrade-insecure-requests: true # defaults to false, upgrades HTTP requests to HTTPS transport + +when@dev: + # disables the Content-Security-Policy header + nelmio_security: + csp: + enabled: false \ No newline at end of file diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index 5fdd5bec..0c4492af 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -1,6 +1,6 @@ twig: default_path: '%kernel.project_dir%/templates' - form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'Form/extendedBootstrap4_layout.html.twig', 'Form/permissionLayout.html.twig' ] + form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'Form/extendedBootstrap4_layout.html.twig', 'Form/permissionLayout.html.twig', 'Form/FilterTypesLayout.html.twig'] paths: '%kernel.project_dir%/assets/css': css diff --git a/src/Controller/AttachmentFileController.php b/src/Controller/AttachmentFileController.php index 9d9ba2f1..34501024 100644 --- a/src/Controller/AttachmentFileController.php +++ b/src/Controller/AttachmentFileController.php @@ -43,9 +43,15 @@ declare(strict_types=1); namespace App\Controller; use App\DataTables\AttachmentDataTable; +use App\DataTables\Filters\AttachmentFilter; +use App\DataTables\Filters\PartFilter; +use App\DataTables\PartsDataTable; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\PartAttachment; +use App\Form\Filters\AttachmentFilterType; +use App\Form\Filters\PartFilterType; use App\Services\Attachments\AttachmentManager; +use App\Services\Trees\NodesListBuilder; use Omines\DataTablesBundle\DataTableFactory; use RuntimeException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -123,11 +129,19 @@ class AttachmentFileController extends AbstractController * * @return JsonResponse|Response */ - public function attachmentsTable(DataTableFactory $dataTable, Request $request) + public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder) { $this->denyAccessUnlessGranted('read', new PartAttachment()); - $table = $dataTable->createFromType(AttachmentDataTable::class) + $formRequest = clone $request; + $formRequest->setMethod('GET'); + $filter = new AttachmentFilter($nodesListBuilder); + + $filterForm = $this->createForm(AttachmentFilterType::class, $filter, ['method' => 'GET']); + + $filterForm->handleRequest($formRequest); + + $table = $dataTableFactory->createFromType(AttachmentDataTable::class, ['filter' => $filter]) ->handleRequest($request); if ($table->isCallback()) { @@ -136,6 +150,7 @@ class AttachmentFileController extends AbstractController return $this->render('attachment_list.html.twig', [ 'datatable' => $table, + 'filterForm' => $filterForm->createView(), ]); } } diff --git a/src/Controller/LogController.php b/src/Controller/LogController.php index 991cd0e4..c4c81df8 100644 --- a/src/Controller/LogController.php +++ b/src/Controller/LogController.php @@ -42,6 +42,7 @@ declare(strict_types=1); namespace App\Controller; +use App\DataTables\Filters\LogFilter; use App\DataTables\LogDataTable; use App\Entity\Base\AbstractDBElement; use App\Entity\LogSystem\AbstractLogEntry; @@ -49,6 +50,7 @@ use App\Entity\LogSystem\CollectionElementDeleted; use App\Entity\LogSystem\ElementCreatedLogEntry; use App\Entity\LogSystem\ElementDeletedLogEntry; use App\Entity\LogSystem\ElementEditedLogEntry; +use App\Form\Filters\LogFilterType; use App\Services\LogSystem\EventUndoHelper; use App\Services\LogSystem\TimeTravel; use Doctrine\ORM\EntityManagerInterface; @@ -86,7 +88,17 @@ class LogController extends AbstractController { $this->denyAccessUnlessGranted('@system.show_logs'); - $table = $dataTable->createFromType(LogDataTable::class) + $formRequest = clone $request; + $formRequest->setMethod('GET'); + $filter = new LogFilter(); + + $filterForm = $this->createForm(LogFilterType::class, $filter, ['method' => 'GET']); + + $filterForm->handleRequest($formRequest); + + $table = $dataTable->createFromType(LogDataTable::class, [ + 'filter' => $filter, + ]) ->handleRequest($request); if ($table->isCallback()) { @@ -95,6 +107,7 @@ class LogController extends AbstractController return $this->render('LogSystem/log_list.html.twig', [ 'datatable' => $table, + 'filterForm' => $filterForm->createView(), ]); } diff --git a/src/Controller/PartListsController.php b/src/Controller/PartListsController.php index 32719591..3c883551 100644 --- a/src/Controller/PartListsController.php +++ b/src/Controller/PartListsController.php @@ -42,16 +42,21 @@ declare(strict_types=1); namespace App\Controller; +use App\DataTables\Filters\PartFilter; +use App\DataTables\Filters\PartSearchFilter; use App\DataTables\PartsDataTable; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Storelocation; use App\Entity\Parts\Supplier; +use App\Form\Filters\PartFilterType; use App\Services\Parts\PartsTableActionHandler; +use App\Services\Trees\NodesListBuilder; use Doctrine\ORM\EntityManagerInterface; use Omines\DataTablesBundle\DataTableFactory; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -60,10 +65,14 @@ use Symfony\Component\Routing\Annotation\Route; class PartListsController extends AbstractController { private $entityManager; + private $nodesListBuilder; + private $dataTableFactory; - public function __construct(EntityManagerInterface $entityManager) + public function __construct(EntityManagerInterface $entityManager, NodesListBuilder $nodesListBuilder, DataTableFactory $dataTableFactory) { $this->entityManager = $entityManager; + $this->nodesListBuilder = $nodesListBuilder; + $this->dataTableFactory = $dataTableFactory; } /** @@ -98,24 +107,81 @@ class PartListsController extends AbstractController } /** - * @Route("/category/{id}/parts", name="part_list_category") - * - * @return JsonResponse|Response + * Disable the given form interface after creation of the form by removing and reattaching the form. + * @param FormInterface $form + * @return void */ - public function showCategory(Category $category, Request $request, DataTableFactory $dataTable) + private function disableFormFieldAfterCreation(FormInterface $form, bool $disabled = true): void { - $table = $dataTable->createFromType(PartsDataTable::class, ['category' => $category]) + $attrs = $form->getConfig()->getOptions(); + $attrs['disabled'] = $disabled; + + $parent = $form->getParent(); + if ($parent === null) { + throw new \RuntimeException('This function can only be used on form fields that are children of another form!'); + } + + $parent->remove($form->getName()); + $parent->add($form->getName(), get_class($form->getConfig()->getType()->getInnerType()), $attrs); + } + + /** + * Common implementation for the part list pages. + * @param Request $request The request to parse + * @param string $template The template that should be rendered + * @param callable|null $filter_changer A function that is called with the filter object as parameter. This function can be used to customize the filter + * @param callable|null $form_changer A function that is called with the form object as parameter. This function can be used to customize the form + * @param array $additonal_template_vars Any additional template variables that should be passed to the template + * @param array $additional_table_vars Any additional variables that should be passed to the table creation + * @return Response + */ + protected function showListWithFilter(Request $request, string $template, ?callable $filter_changer = null, ?callable $form_changer = null, array $additonal_template_vars = [], array $additional_table_vars = []): Response + { + $formRequest = clone $request; + $formRequest->setMethod('GET'); + $filter = new PartFilter($this->nodesListBuilder); + if($filter_changer !== null){ + $filter_changer($filter); + } + + $filterForm = $this->createForm(PartFilterType::class, $filter, ['method' => 'GET']); + if($form_changer !== null) { + $form_changer($filterForm); + } + + $filterForm->handleRequest($formRequest); + + $table = $this->dataTableFactory->createFromType(PartsDataTable::class, array_merge(['filter' => $filter], $additional_table_vars)) ->handleRequest($request); if ($table->isCallback()) { return $table->getResponse(); } - return $this->render('Parts/lists/category_list.html.twig', [ + return $this->render($template, array_merge([ 'datatable' => $table, - 'entity' => $category, - 'repo' => $this->entityManager->getRepository(Category::class), - ]); + 'filterForm' => $filterForm->createView(), + ], $additonal_template_vars)); + } + + /** + * @Route("/category/{id}/parts", name="part_list_category") + * + * @return JsonResponse|Response + */ + public function showCategory(Category $category, Request $request) + { + return $this->showListWithFilter($request, + 'Parts/lists/category_list.html.twig', + function (PartFilter $filter) use ($category) { + $filter->getCategory()->setOperator('INCLUDING_CHILDREN')->setValue($category); + }, function (FormInterface $filterForm) { + $this->disableFormFieldAfterCreation($filterForm->get('category')->get('value')); + }, [ + 'entity' => $category, + 'repo' => $this->entityManager->getRepository(Category::class), + ] + ); } /** @@ -123,20 +189,19 @@ class PartListsController extends AbstractController * * @return JsonResponse|Response */ - public function showFootprint(Footprint $footprint, Request $request, DataTableFactory $dataTable) + public function showFootprint(Footprint $footprint, Request $request) { - $table = $dataTable->createFromType(PartsDataTable::class, ['footprint' => $footprint]) - ->handleRequest($request); - - if ($table->isCallback()) { - return $table->getResponse(); - } - - return $this->render('Parts/lists/footprint_list.html.twig', [ - 'datatable' => $table, - 'entity' => $footprint, - 'repo' => $this->entityManager->getRepository(Footprint::class), - ]); + return $this->showListWithFilter($request, + 'Parts/lists/footprint_list.html.twig', + function (PartFilter $filter) use ($footprint) { + $filter->getFootprint()->setOperator('INCLUDING_CHILDREN')->setValue($footprint); + }, function (FormInterface $filterForm) { + $this->disableFormFieldAfterCreation($filterForm->get('footprint')->get('value')); + }, [ + 'entity' => $footprint, + 'repo' => $this->entityManager->getRepository(Footprint::class), + ] + ); } /** @@ -144,20 +209,19 @@ class PartListsController extends AbstractController * * @return JsonResponse|Response */ - public function showManufacturer(Manufacturer $manufacturer, Request $request, DataTableFactory $dataTable) + public function showManufacturer(Manufacturer $manufacturer, Request $request) { - $table = $dataTable->createFromType(PartsDataTable::class, ['manufacturer' => $manufacturer]) - ->handleRequest($request); - - if ($table->isCallback()) { - return $table->getResponse(); - } - - return $this->render('Parts/lists/manufacturer_list.html.twig', [ - 'datatable' => $table, - 'entity' => $manufacturer, - 'repo' => $this->entityManager->getRepository(Manufacturer::class), - ]); + return $this->showListWithFilter($request, + 'Parts/lists/manufacturer_list.html.twig', + function (PartFilter $filter) use ($manufacturer) { + $filter->getManufacturer()->setOperator('INCLUDING_CHILDREN')->setValue($manufacturer); + }, function (FormInterface $filterForm) { + $this->disableFormFieldAfterCreation($filterForm->get('manufacturer')->get('value')); + }, [ + 'entity' => $manufacturer, + 'repo' => $this->entityManager->getRepository(Manufacturer::class), + ] + ); } /** @@ -165,20 +229,19 @@ class PartListsController extends AbstractController * * @return JsonResponse|Response */ - public function showStorelocation(Storelocation $storelocation, Request $request, DataTableFactory $dataTable) + public function showStorelocation(Storelocation $storelocation, Request $request) { - $table = $dataTable->createFromType(PartsDataTable::class, ['storelocation' => $storelocation]) - ->handleRequest($request); - - if ($table->isCallback()) { - return $table->getResponse(); - } - - return $this->render('Parts/lists/store_location_list.html.twig', [ - 'datatable' => $table, - 'entity' => $storelocation, - 'repo' => $this->entityManager->getRepository(Storelocation::class), - ]); + return $this->showListWithFilter($request, + 'Parts/lists/store_location_list.html.twig', + function (PartFilter $filter) use ($storelocation) { + $filter->getStorelocation()->setOperator('INCLUDING_CHILDREN')->setValue($storelocation); + }, function (FormInterface $filterForm) { + $this->disableFormFieldAfterCreation($filterForm->get('storelocation')->get('value')); + }, [ + 'entity' => $storelocation, + 'repo' => $this->entityManager->getRepository(Storelocation::class), + ] + ); } /** @@ -186,20 +249,19 @@ class PartListsController extends AbstractController * * @return JsonResponse|Response */ - public function showSupplier(Supplier $supplier, Request $request, DataTableFactory $dataTable) + public function showSupplier(Supplier $supplier, Request $request) { - $table = $dataTable->createFromType(PartsDataTable::class, ['supplier' => $supplier]) - ->handleRequest($request); - - if ($table->isCallback()) { - return $table->getResponse(); - } - - return $this->render('Parts/lists/supplier_list.html.twig', [ - 'datatable' => $table, - 'entity' => $supplier, - 'repo' => $this->entityManager->getRepository(Supplier::class), - ]); + return $this->showListWithFilter($request, + 'Parts/lists/supplier_list.html.twig', + function (PartFilter $filter) use ($supplier) { + $filter->getSupplier()->setOperator('INCLUDING_CHILDREN')->setValue($supplier); + }, function (FormInterface $filterForm) { + $this->disableFormFieldAfterCreation($filterForm->get('supplier')->get('value')); + }, [ + 'entity' => $supplier, + 'repo' => $this->entityManager->getRepository(Supplier::class), + ] + ); } /** @@ -210,17 +272,37 @@ class PartListsController extends AbstractController public function showTag(string $tag, Request $request, DataTableFactory $dataTable) { $tag = trim($tag); - $table = $dataTable->createFromType(PartsDataTable::class, ['tag' => $tag]) - ->handleRequest($request); - if ($table->isCallback()) { - return $table->getResponse(); - } + return $this->showListWithFilter($request, + 'Parts/lists/tags_list.html.twig', + function (PartFilter $filter) use ($tag) { + $filter->getTags()->setOperator('ANY')->setValue($tag); + }, function (FormInterface $filterForm) { + $this->disableFormFieldAfterCreation($filterForm->get('tags')->get('value')); + }, [ + 'tag' => $tag, + ] + ); + } - return $this->render('Parts/lists/tags_list.html.twig', [ - 'tag' => $tag, - 'datatable' => $table, - ]); + private function searchRequestToFilter(Request $request): PartSearchFilter + { + $filter = new PartSearchFilter($request->query->get('keyword', '')); + + $filter->setName($request->query->getBoolean('name', true)); + $filter->setCategory($request->query->getBoolean('category', true)); + $filter->setDescription($request->query->getBoolean('description', true)); + $filter->setTags($request->query->getBoolean('tags', true)); + $filter->setStorelocation($request->query->getBoolean('storelocation', true)); + $filter->setComment($request->query->getBoolean('comment', true)); + $filter->setOrdernr($request->query->getBoolean('ordernr', true)); + $filter->setSupplier($request->query->getBoolean('supplier', false)); + $filter->setManufacturer($request->query->getBoolean('manufacturer', false)); + $filter->setFootprint($request->query->getBoolean('footprint', false)); + + $filter->setRegex($request->query->getBoolean('regex', false)); + + return $filter; } /** @@ -230,35 +312,20 @@ class PartListsController extends AbstractController */ public function showSearch(Request $request, DataTableFactory $dataTable) { - $search = $request->query->get('keyword', ''); - $search_options = [ - 'name' => $request->query->getBoolean('name'), - 'description' => $request->query->getBoolean('description'), - 'comment' => $request->query->getBoolean('comment'), - 'category' => $request->query->getBoolean('category'), - 'store_location' => $request->query->getBoolean('storelocation'), - 'supplier' => $request->query->getBoolean('supplier'), - 'ordernr' => $request->query->getBoolean('ordernr'), - 'manufacturer' => $request->query->getBoolean('manufacturer'), - 'footprint' => $request->query->getBoolean('footprint'), - 'tags' => $request->query->getBoolean('tags'), - 'regex' => $request->query->getBoolean('regex'), - ]; + $searchFilter = $this->searchRequestToFilter($request); - $table = $dataTable->createFromType(PartsDataTable::class, [ - 'search' => $search, - 'search_options' => $search_options, - ]) - ->handleRequest($request); - - if ($table->isCallback()) { - return $table->getResponse(); - } - - return $this->render('Parts/lists/search_list.html.twig', [ - 'datatable' => $table, - 'keyword' => $search, - ]); + return $this->showListWithFilter($request, + 'Parts/lists/search_list.html.twig', + null, + null, + [ + 'keyword' => $searchFilter->getKeyword(), + 'searchFilter' => $searchFilter, + ], + [ + 'search' => $searchFilter, + ] + ); } /** @@ -268,13 +335,6 @@ class PartListsController extends AbstractController */ public function showAll(Request $request, DataTableFactory $dataTable) { - $table = $dataTable->createFromType(PartsDataTable::class) - ->handleRequest($request); - - if ($table->isCallback()) { - return $table->getResponse(); - } - - return $this->render('Parts/lists/all_list.html.twig', ['datatable' => $table]); + return $this->showListWithFilter($request,'Parts/lists/all_list.html.twig'); } } diff --git a/src/Controller/TypeaheadController.php b/src/Controller/TypeaheadController.php index f69f137e..77edae80 100644 --- a/src/Controller/TypeaheadController.php +++ b/src/Controller/TypeaheadController.php @@ -42,9 +42,22 @@ declare(strict_types=1); namespace App\Controller; +use App\Entity\Parameters\AttachmentTypeParameter; +use App\Entity\Parameters\CategoryParameter; +use App\Entity\Parameters\DeviceParameter; +use App\Entity\Parameters\FootprintParameter; +use App\Entity\Parameters\GroupParameter; +use App\Entity\Parameters\ManufacturerParameter; +use App\Entity\Parameters\MeasurementUnitParameter; +use App\Entity\Parameters\PartParameter; +use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\SupplierParameter; +use App\Entity\PriceInformations\Currency; +use App\Repository\ParameterRepository; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\Attachments\BuiltinAttachmentsFinder; use App\Services\TagFinder; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Asset\Packages; use Symfony\Component\HttpFoundation\JsonResponse; @@ -99,6 +112,58 @@ class TypeaheadController extends AbstractController return new JsonResponse($data, 200, [], true); } + /** + * This functions map the parameter type to the class, so we can access its repository + * @param string $type + * @return class-string + */ + private function typeToParameterClass(string $type): string + { + switch ($type) { + case 'category': + return CategoryParameter::class; + case 'part': + return PartParameter::class; + case 'device': + return DeviceParameter::class; + case 'footprint': + return FootprintParameter::class; + case 'manufacturer': + return ManufacturerParameter::class; + case 'storelocation': + return StorelocationParameter::class; + case 'supplier': + return SupplierParameter::class; + case 'attachment_type': + return AttachmentTypeParameter::class; + case 'group': + return GroupParameter::class; + case 'measurement_unit': + return MeasurementUnitParameter::class; + case 'currency': + return Currency::class; + + default: + throw new \InvalidArgumentException('Invalid parameter type: '.$type); + } + } + + /** + * @Route("/parameters/{type}/search/{query}", name="typeahead_parameters", requirements={"type" = ".+"}) + * @param string $query + * @return JsonResponse + */ + public function parameters(string $type, EntityManagerInterface $entityManager, string $query = ""): JsonResponse + { + $class = $this->typeToParameterClass($type); + /** @var ParameterRepository $repository */ + $repository = $entityManager->getRepository($class); + + $data = $repository->autocompleteParamName($query); + + return new JsonResponse($data); + } + /** * @Route("/tags/search/{query}", name="typeahead_tags", requirements={"query"= ".+"}) */ diff --git a/src/DataTables/AttachmentDataTable.php b/src/DataTables/AttachmentDataTable.php index 3a113ccf..122d3abe 100644 --- a/src/DataTables/AttachmentDataTable.php +++ b/src/DataTables/AttachmentDataTable.php @@ -43,14 +43,16 @@ declare(strict_types=1); namespace App\DataTables; use App\DataTables\Column\LocaleDateTimeColumn; +use App\DataTables\Column\PrettyBoolColumn; +use App\DataTables\Filters\AttachmentFilter; use App\Entity\Attachments\Attachment; use App\Services\Attachments\AttachmentManager; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\ElementTypeNameGenerator; use App\Services\EntityURLGenerator; use Doctrine\ORM\QueryBuilder; +use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider; use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter; -use Omines\DataTablesBundle\Column\BoolColumn; use Omines\DataTablesBundle\Column\TextColumn; use Omines\DataTablesBundle\DataTable; use Omines\DataTablesBundle\DataTableTypeInterface; @@ -79,6 +81,7 @@ final class AttachmentDataTable implements DataTableTypeInterface { $dataTable->add('picture', TextColumn::class, [ 'label' => '', + 'className' => 'no-colvis', 'render' => function ($value, Attachment $context) { if ($context->isPicture() && !$context->isExternal() @@ -166,7 +169,7 @@ final class AttachmentDataTable implements DataTableTypeInterface } return sprintf( - ' + ' %s ', $this->translator->trans('attachment.file_not_found') @@ -184,37 +187,25 @@ final class AttachmentDataTable implements DataTableTypeInterface 'visible' => false, ]); - $dataTable->add('show_in_table', BoolColumn::class, [ + $dataTable->add('show_in_table', PrettyBoolColumn::class, [ 'label' => 'attachment.edit.show_in_table', - 'trueValue' => $this->translator->trans('true'), - 'falseValue' => $this->translator->trans('false'), - 'nullValue' => '', 'visible' => false, ]); - $dataTable->add('isPicture', BoolColumn::class, [ + $dataTable->add('isPicture', PrettyBoolColumn::class, [ 'label' => 'attachment.edit.isPicture', - 'trueValue' => $this->translator->trans('true'), - 'falseValue' => $this->translator->trans('false'), - 'nullValue' => '', 'visible' => false, 'propertyPath' => 'picture', ]); - $dataTable->add('is3DModel', BoolColumn::class, [ + $dataTable->add('is3DModel', PrettyBoolColumn::class, [ 'label' => 'attachment.edit.is3DModel', - 'trueValue' => $this->translator->trans('true'), - 'falseValue' => $this->translator->trans('false'), - 'nullValue' => '', 'visible' => false, 'propertyPath' => '3dmodel', ]); - $dataTable->add('isBuiltin', BoolColumn::class, [ + $dataTable->add('isBuiltin', PrettyBoolColumn::class, [ 'label' => 'attachment.edit.isBuiltin', - 'trueValue' => $this->translator->trans('true'), - 'falseValue' => $this->translator->trans('false'), - 'nullValue' => '', 'visible' => false, 'propertyPath' => 'builtin', ]); @@ -224,6 +215,12 @@ final class AttachmentDataTable implements DataTableTypeInterface 'query' => function (QueryBuilder $builder): void { $this->getQuery($builder); }, + 'criteria' => [ + function (QueryBuilder $builder) use ($options): void { + $this->buildCriteria($builder, $options); + }, + new SearchCriteriaProvider(), + ], ]); } @@ -236,4 +233,18 @@ final class AttachmentDataTable implements DataTableTypeInterface ->leftJoin('attachment.attachment_type', 'attachment_type'); //->leftJoin('attachment.element', 'element'); } + + private function buildCriteria(QueryBuilder $builder, array $options): void + { + //We do the most stuff here in the filter class + if (isset($options['filter'])) { + if(!$options['filter'] instanceof AttachmentFilter) { + throw new \Exception('filter must be an instance of AttachmentFilter!'); + } + + $filter = $options['filter']; + $filter->apply($builder); + } + + } } diff --git a/src/DataTables/Column/LocaleDateTimeColumn.php b/src/DataTables/Column/LocaleDateTimeColumn.php index f9103f86..c380bada 100644 --- a/src/DataTables/Column/LocaleDateTimeColumn.php +++ b/src/DataTables/Column/LocaleDateTimeColumn.php @@ -58,7 +58,7 @@ class LocaleDateTimeColumn extends AbstractColumn { /** * @param $value - * @return bool|mixed|string + * @return string * @throws Exception */ public function normalize($value): string diff --git a/src/DataTables/Column/PrettyBoolColumn.php b/src/DataTables/Column/PrettyBoolColumn.php new file mode 100644 index 00000000..da6af1c4 --- /dev/null +++ b/src/DataTables/Column/PrettyBoolColumn.php @@ -0,0 +1,49 @@ +translator = $translator; + } + + public function normalize($value): ?bool + { + if (null === $value) { + return null; + } + + return (bool) $value; + } + + public function render($value, $context) + { + if ($value === true) { + return ' ' + . $this->translator->trans('bool.true') + . ''; + } + + if ($value === false) { + return ' ' + . $this->translator->trans('bool.false') + . ''; + } + + if ($value === null) { + return ' ' + . $this->translator->trans('bool.unknown') + . ''; + } + + throw new \RuntimeException('Unexpected value!'); + } +} \ No newline at end of file diff --git a/src/DataTables/Column/SIUnitNumberColumn.php b/src/DataTables/Column/SIUnitNumberColumn.php new file mode 100644 index 00000000..77d2a197 --- /dev/null +++ b/src/DataTables/Column/SIUnitNumberColumn.php @@ -0,0 +1,37 @@ +formatter = $formatter; + } + + public function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + + $resolver->setDefault('precision', 2); + $resolver->setDefault('unit', ''); + + return $this; + } + + public function normalize($value) + { + //Ignore null values + if ($value === null) { + return ''; + } + + return $this->formatter->format((float) $value, $this->options['unit'], $this->options['precision']); + } +} \ No newline at end of file diff --git a/src/DataTables/Column/SelectColumn.php b/src/DataTables/Column/SelectColumn.php new file mode 100644 index 00000000..b4dd17e8 --- /dev/null +++ b/src/DataTables/Column/SelectColumn.php @@ -0,0 +1,38 @@ +setDefaults([ + 'label' => '', + 'orderable' => false, + 'searchable' => false, + 'className' => 'select-checkbox no-colvis', + 'visible' => true, + ]); + + return $this; + } + + public function normalize($value) + { + return $value; + } + + public function render($value, $context) + { + //Return empty string, as it this column is filled by datatables on client side + return ''; + } +} \ No newline at end of file diff --git a/src/DataTables/Column/TagsColumn.php b/src/DataTables/Column/TagsColumn.php index b0364cd7..d02dc1f9 100644 --- a/src/DataTables/Column/TagsColumn.php +++ b/src/DataTables/Column/TagsColumn.php @@ -79,7 +79,7 @@ class TagsColumn extends AbstractColumn break; } $html .= sprintf( - '%s', + '%s', $this->urlGenerator->generate('part_list_tags', ['tag' => $tag]), htmlspecialchars($tag) ); diff --git a/src/DataTables/Filters/AttachmentFilter.php b/src/DataTables/Filters/AttachmentFilter.php new file mode 100644 index 00000000..bbdadca2 --- /dev/null +++ b/src/DataTables/Filters/AttachmentFilter.php @@ -0,0 +1,121 @@ +dbId = new IntConstraint('attachment.id'); + $this->name = new TextConstraint('attachment.name'); + $this->targetType = new InstanceOfConstraint('attachment'); + $this->attachmentType = new EntityConstraint($nodesListBuilder, AttachmentType::class, 'attachment.attachment_type'); + $this->lastModified = new DateTimeConstraint('attachment.lastModified'); + $this->addedDate = new DateTimeConstraint('attachment.addedDate'); + $this->showInTable = new BooleanConstraint('attachment.show_in_table'); + } + + public function apply(QueryBuilder $queryBuilder): void + { + $this->applyAllChildFilters($queryBuilder); + } + + /** + * @return NumberConstraint + */ + public function getDbId(): NumberConstraint + { + return $this->dbId; + } + + /** + * @return TextConstraint + */ + public function getName(): TextConstraint + { + return $this->name; + } + + /** + * @return DateTimeConstraint + */ + public function getLastModified(): DateTimeConstraint + { + return $this->lastModified; + } + + /** + * @return DateTimeConstraint + */ + public function getAddedDate(): DateTimeConstraint + { + return $this->addedDate; + } + + + /** + * @return BooleanConstraint + */ + public function getShowInTable(): BooleanConstraint + { + return $this->showInTable; + } + + + /** + * @return EntityConstraint + */ + public function getAttachmentType(): EntityConstraint + { + return $this->attachmentType; + } + + /** + * @return InstanceOfConstraint + */ + public function getTargetType(): InstanceOfConstraint + { + return $this->targetType; + } + + + + + + +} \ No newline at end of file diff --git a/src/DataTables/Filters/CompoundFilterTrait.php b/src/DataTables/Filters/CompoundFilterTrait.php new file mode 100644 index 00000000..540e291e --- /dev/null +++ b/src/DataTables/Filters/CompoundFilterTrait.php @@ -0,0 +1,57 @@ + $filter_object + * @return FilterInterface[] + */ + protected function findAllChildFilters(): array + { + $filters = []; + $reflection = new \ReflectionClass($this); + + foreach ($reflection->getProperties() as $property) { + //Set property to accessible (otherwise we run into problems on PHP < 8.1) + $property->setAccessible(true); + + $value = $property->getValue($this); + //We only want filters (objects implementing FilterInterface) + if($value instanceof FilterInterface) { + $filters[$property->getName()] = $value; + } + + //Add filters in collections + if ($value instanceof Collection) { + foreach ($value as $key => $filter) { + if($filter instanceof FilterInterface) { + $filters[$property->getName() . '.' . (string) $key] = $filter; + } + } + } + } + return $filters; + } + + /** + * Applies all children filters that are declared as property of this filter using reflection. + * @param QueryBuilder $queryBuilder + * @return void + */ + protected function applyAllChildFilters(QueryBuilder $queryBuilder): void + { + //Retrieve all child filters and apply them + $filters = $this->findAllChildFilters(); + + foreach ($filters as $filter) { + $filter->apply($queryBuilder); + } + } +} \ No newline at end of file diff --git a/src/DataTables/Filters/Constraints/AbstractConstraint.php b/src/DataTables/Filters/Constraints/AbstractConstraint.php new file mode 100644 index 00000000..0af33ce5 --- /dev/null +++ b/src/DataTables/Filters/Constraints/AbstractConstraint.php @@ -0,0 +1,34 @@ +property = $property; + $this->identifier = $identifier ?? $this->generateParameterIdentifier($property); + } +} \ No newline at end of file diff --git a/src/DataTables/Filters/Constraints/BooleanConstraint.php b/src/DataTables/Filters/Constraints/BooleanConstraint.php new file mode 100644 index 00000000..c72c7c6e --- /dev/null +++ b/src/DataTables/Filters/Constraints/BooleanConstraint.php @@ -0,0 +1,53 @@ +value = $default_value; + } + + /** + * Gets the value of this constraint. Null means "don't filter", true means "filter for true", false means "filter for false". + * @return bool|null + */ + public function getValue(): ?bool + { + return $this->value; + } + + /** + * Sets the value of this constraint. Null means "don't filter", true means "filter for true", false means "filter for false". + * @param bool|null $value + */ + public function setValue(?bool $value): void + { + $this->value = $value; + } + + public function isEnabled(): bool + { + return $this->value !== null; + } + + + public function apply(QueryBuilder $queryBuilder): void + { + //Do not apply a filter if value is null (filter is set to ignore) + if(!$this->isEnabled()) { + return; + } + + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, '=', $this->value); + } +} \ No newline at end of file diff --git a/src/DataTables/Filters/Constraints/ChoiceConstraint.php b/src/DataTables/Filters/Constraints/ChoiceConstraint.php new file mode 100644 index 00000000..63737cc0 --- /dev/null +++ b/src/DataTables/Filters/Constraints/ChoiceConstraint.php @@ -0,0 +1,84 @@ +value; + } + + /** + * @param string[]|int[] $value + * @return ChoiceConstraint + */ + public function setValue(array $value): ChoiceConstraint + { + $this->value = $value; + return $this; + } + + /** + * @return string + */ + public function getOperator(): string + { + return $this->operator; + } + + /** + * @param string $operator + * @return ChoiceConstraint + */ + public function setOperator(string $operator): ChoiceConstraint + { + $this->operator = $operator; + return $this; + } + + + + public function isEnabled(): bool + { + return !empty($this->operator); + } + + public function apply(QueryBuilder $queryBuilder): void + { + //If no value is provided then we do not apply a filter + if (!$this->isEnabled()) { + return; + } + + //Ensure we have an valid operator + if(!in_array($this->operator, self::ALLOWED_OPERATOR_VALUES, true)) { + throw new \RuntimeException('Invalid operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES)); + } + + if ($this->operator === 'ANY') { + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'IN', $this->value); + } elseif ($this->operator === 'NONE') { + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'NOT IN', $this->value); + } else { + throw new \RuntimeException('Unknown operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES)); + } + } +} \ No newline at end of file diff --git a/src/DataTables/Filters/Constraints/DateTimeConstraint.php b/src/DataTables/Filters/Constraints/DateTimeConstraint.php new file mode 100644 index 00000000..4cc3f50f --- /dev/null +++ b/src/DataTables/Filters/Constraints/DateTimeConstraint.php @@ -0,0 +1,10 @@ + The class to use for the comparison + */ + protected $class; + + /** + * @var string|null The operator to use + */ + protected $operator; + + /** + * @var T The value to compare to + */ + protected $value; + + /** + * @param NodesListBuilder|null $nodesListBuilder + * @param class-string