Merge pull request #178 from LDAPAccountManager/feature/sortable

Feature/sortable
This commit is contained in:
gruberroland 2022-05-29 19:49:58 +02:00 committed by GitHub
commit 42dad06f6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 3960 additions and 54 deletions

View file

@ -1,3 +1,3 @@
#!/bin/bash #!/bin/bash
~/.local/bin/codespell --skip '*3rdParty*,*/ckeditor/*,*/po/*,*/locale/*,tmp,sess,config,graphics,*/style/images/*,*/style/*.gif,*/style/*.png,*/docs/manual-onePage/*,*/docs/manual-sources/images/*,*/templates/lib/*jquery*,*/templates/lib/*flatpickr*,*~,*/docs/phpdoc/*,*/docs/manual/*,*/docs/devel/images/*,*/docs/manual-pdf/*,*.sh,*/cropper.js,*/lib/extra/*,lam/.phpdoc' --ignore-words-list "tim,te,pres,files'" lam ~/.local/bin/codespell --skip '*3rdParty*,*/ckeditor/*,*/po/*,*/locale/*,tmp,sess,config,graphics,*/style/images/*,*/style/*.gif,*/style/*.png,*/docs/manual-onePage/*,*/docs/manual-sources/images/*,*/templates/lib/*jquery*,*/templates/lib/*flatpickr*,*/templates/lib/*Sortable*,*~,*/docs/phpdoc/*,*/docs/manual/*,*/docs/devel/images/*,*/docs/manual-pdf/*,*.sh,*/cropper.js,*/lib/extra/*,lam/.phpdoc' --ignore-words-list "tim,te,pres,files'" lam

View file

@ -1197,6 +1197,7 @@ templates/lib/extra/duo/*.js E 2019 Du
lib/3rdParty/duo/*.php E 2019 Duo Security lib/3rdParty/duo/*.php E 2019 Duo Security
templates/lib/extra/friendlyCaptcha B templates/lib/extra/friendlyCaptcha B
graphics/webauthn.svg F 2017 Duo Security, Inc. graphics/webauthn.svg F 2017 Duo Security, Inc.
templates/lib/400_Sortable*.js B RubaXa, owenm
templates/lib/600_jquery.magnific-popup.js B 2016 Dmitry Semenov templates/lib/600_jquery.magnific-popup.js B 2016 Dmitry Semenov
style/610_magnific-popup.css B 2016 Dmitry Semenov style/610_magnific-popup.css B 2016 Dmitry Semenov
style/010_normalize.css B Nicolas Gallagher and Jonathan Neal style/010_normalize.css B Nicolas Gallagher and Jonathan Neal

View file

@ -5,7 +5,7 @@ June 2022 8.0
- 389ds: added hints why login failed if account is locked/deactivated/expired - 389ds: added hints why login failed if account is locked/deactivated/expired
- Removed Zarafa support (please switch to Kopano) - Removed Zarafa support (please switch to Kopano)
- Tree view: display binary data as base64 encoded text - Tree view: display binary data as base64 encoded text
- Tree view: better support for move operations - Tree view: better support for move operations and ordered attributes
- LAM Pro: - LAM Pro:
-> New captcha providers: hCaptcha and Friendly Captcha -> New captcha providers: hCaptcha and Friendly Captcha
- Fixed bugs: - Fixed bugs:

View file

@ -1196,6 +1196,7 @@ templates/lib/extra/duo/*.js E 2019 Du
templates/lib/extra/friendlyCaptcha B templates/lib/extra/friendlyCaptcha B
lib/3rdParty/duo/*.php E 2019 Duo Security lib/3rdParty/duo/*.php E 2019 Duo Security
graphics/webauthn.svg F 2017 Duo Security, Inc. graphics/webauthn.svg F 2017 Duo Security, Inc.
templates/lib/400_Sortable*.js B RubaXa, owenm
templates/lib/600_jquery.magnific-popup.js B 2016 Dmitry Semenov templates/lib/600_jquery.magnific-popup.js B 2016 Dmitry Semenov
style/610_magnific-popup.css B 2016 Dmitry Semenov style/610_magnific-popup.css B 2016 Dmitry Semenov
style/010_normalize.css B Nicolas Gallagher and Jonathan Neal style/010_normalize.css B Nicolas Gallagher and Jonathan Neal

View file

@ -4099,8 +4099,6 @@ class htmlSortableList extends htmlElement {
private $elements = array(); private $elements = array();
/** HTML ID */ /** HTML ID */
private $id = ''; private $id = '';
/** element width */
private $elementWidth = '';
/** on update event */ /** on update event */
private $onUpdate = null; private $onUpdate = null;
@ -4109,12 +4107,10 @@ class htmlSortableList extends htmlElement {
* *
* @param array $elements list of elements as text (HTML special chars must be escaped already) or htmlElement * @param array $elements list of elements as text (HTML special chars must be escaped already) or htmlElement
* @param String HTML ID * @param String HTML ID
* @param String $elementWidth width of elements (default 250px)
*/ */
function __construct($elements, $id, $elementWidth='250px') { function __construct($elements, $id) {
$this->elements = $elements; $this->elements = $elements;
$this->id = htmlspecialchars($id); $this->id = htmlspecialchars($id);
$this->elementWidth = $elementWidth;
} }
/** /**
@ -4133,9 +4129,15 @@ class htmlSortableList extends htmlElement {
return array(); return array();
} }
$return = array(); $return = array();
echo '<ul style="width:' . $this->elementWidth . ';" class="sortableList" id="' . $this->id . '">'; $classesValue = '';
foreach ($this->elements as $element) { if (!empty($this->cssClasses)) {
echo '<li class="ui-state-default"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>'; $classesValue = ' class="' . implode(' ', $this->cssClasses) . '"';
}
echo '<ul' . $classesValue . ' id="' . $this->id . '">';
for ($i = 0; $i < sizeof($this->elements); $i++) {
$element = $this->elements[$i];
echo '<li data-position-orig="' . $i . '">';
echo '<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>';
if ($element instanceof htmlElement) { if ($element instanceof htmlElement) {
parseHtml($module, $element, $values, $restricted, $tabindex, $scope); parseHtml($module, $element, $values, $restricted, $tabindex, $scope);
} }
@ -4147,19 +4149,15 @@ class htmlSortableList extends htmlElement {
echo '</ul>'; echo '</ul>';
$onUpdate = ''; $onUpdate = '';
if ($this->onUpdate != null) { if ($this->onUpdate != null) {
$onUpdate = '{ $onUpdate = 'onUpdate: ' . $this->onUpdate;
update: function(event, ui) {' . $this->onUpdate . '},
start: function(event, ui) {
var posOrig = ui.item.index();
ui.item.data(\'posOrig\', posOrig);
}
}';
} }
$scriptContent = ' $scriptContent = '
jQuery(function() { Sortable.create(
$("#' . $this->id . '").sortable(' . $onUpdate . '); document.getElementById("' . $this->id . '"),
$("#' . $this->id . '").disableSelection(); {
});'; ' . $onUpdate . '
}
);';
$script = new htmlJavaScript($scriptContent); $script = new htmlJavaScript($scriptContent);
$script->generateHTML($module, $input, $values, $restricted, $tabindex, $scope); $script->generateHTML($module, $input, $values, $restricted, $tabindex, $scope);
return $return; return $return;
@ -5233,3 +5231,50 @@ class htmlForm extends htmlElement {
} }
/**
* Represents a (un)ordered list.
*/
class htmlList extends htmlElement {
private $elements = array();
private $isOrdered = false;
private $id = '';
/**
* Constructor.
*
* @param array $elements elements to render
* @param string|null $id HTML unique id
* @param bool|null $isOrdered is an ordered list
*/
function __construct(array $elements, ?string $id = null, ?bool $isOrdered = false) {
$this->elements = $elements;
$this->id = $id;
$this->isOrdered = $isOrdered;
}
/**
* {@inheritDoc}
*/
function generateHTML($module, $input, $values, $restricted, &$tabindex, $scope) {
$parentTag = $this->isOrdered ? 'ol' : 'ul';
$idValue = empty($this->id) ? '' : ' id="' . $this->id . '"';
$classesValue = '';
if (!empty($this->cssClasses)) {
$classesValue = ' class="' . implode(' ', $this->cssClasses) . '"';
}
echo '<' . $parentTag . $idValue . $classesValue . '>';
$return = array();
foreach ($this->elements as $element) {
echo '<li>';
$return = array_merge($return, $element->generateHTML($module, $input, $values, $restricted, $tabindex, $scope));
echo '</li>';
}
echo '</' . $parentTag . '>';
return $return;
}
}

View file

@ -11,12 +11,14 @@ use htmlInputField;
use htmlInputFileUpload; use htmlInputFileUpload;
use htmlInputTextarea; use htmlInputTextarea;
use htmlLink; use htmlLink;
use htmlList;
use htmlOutputText; use htmlOutputText;
use htmlResponsiveInputField; use htmlResponsiveInputField;
use htmlResponsiveRow; use htmlResponsiveRow;
use htmlResponsiveSelect; use htmlResponsiveSelect;
use htmlResponsiveTable; use htmlResponsiveTable;
use htmlSelect; use htmlSelect;
use htmlSortableList;
use htmlStatusMessage; use htmlStatusMessage;
use htmlSubTitle; use htmlSubTitle;
use htmlTable; use htmlTable;
@ -518,11 +520,11 @@ class TreeView {
$field = $this->getAttributeInputField($attributeName, $values[0], $required, $isMultiLine, true, $schemaAttribute, 0); $field = $this->getAttributeInputField($attributeName, $values[0], $required, $isMultiLine, true, $schemaAttribute, 0);
return $this->addExtraAttributeContent($field, $attributeName, $schemaAttribute); return $this->addExtraAttributeContent($field, $attributeName, $schemaAttribute);
} }
$valueTable = new htmlTable(); $valueListContents = array();
$valueTable->setCSSClasses(array('fullwidth'));
$autoCompleteValues = array(); $autoCompleteValues = array();
$onInput = null; $onInput = null;
if (strtolower($attributeName) === 'objectclass') { $safeAttributeName = htmlspecialchars(strtolower($attributeName));
if ($safeAttributeName === 'objectclass') {
$schemaObjectClasses = $this->getSchemaObjectClasses(); $schemaObjectClasses = $this->getSchemaObjectClasses();
foreach ($schemaObjectClasses as $schemaObjectClass) { foreach ($schemaObjectClasses as $schemaObjectClass) {
if (!in_array_ignore_case($schemaObjectClass->getName(), $values)) { if (!in_array_ignore_case($schemaObjectClass->getName(), $values)) {
@ -534,7 +536,7 @@ class TreeView {
foreach ($values as $index => $value) { foreach ($values as $index => $value) {
$inputField = $this->getAttributeInputField($attributeName, $value, $required, $isMultiLine, false, $schemaAttribute, $index); $inputField = $this->getAttributeInputField($attributeName, $value, $required, $isMultiLine, false, $schemaAttribute, $index);
$cssClasses = $inputField->getCSSClasses(); $cssClasses = $inputField->getCSSClasses();
$cssClasses[] = 'lam-attr-' . htmlspecialchars(strtolower($attributeName)); $cssClasses[] = 'lam-attr-' . $safeAttributeName;
$inputField->setCSSClasses($cssClasses); $inputField->setCSSClasses($cssClasses);
if (!empty($autoCompleteValues) && ($inputField instanceof htmlInputField)) { if (!empty($autoCompleteValues) && ($inputField instanceof htmlInputField)) {
$inputField->enableAutocompletion($autoCompleteValues); $inputField->enableAutocompletion($autoCompleteValues);
@ -543,24 +545,56 @@ class TreeView {
if ($onInput !== null) { if ($onInput !== null) {
$inputField->setOnInput($onInput); $inputField->setOnInput($onInput);
} }
$valueTable->addElement($inputField); $valueLine = new htmlTable();
$valueLine->setCSSClasses(array('fullwidth'));
$valueLine->addElement($inputField);
if (checkIfWriteAccessIsAllowed()) { if (checkIfWriteAccessIsAllowed()) {
if (!$this->isJpegAttribute($attributeName, $schemaAttribute)) { if (!$this->isJpegAttribute($attributeName, $schemaAttribute)) {
$addButton = new htmlLink(null, '#', '../../graphics/add.svg'); $addButton = new htmlLink(null, '#', '../../graphics/add.svg');
$addButton->setCSSClasses(array('margin2')); $addButton->setCSSClasses(array('margin2'));
$addButton->setOnClick('window.lam.treeview.addValue(event, this);'); $addButton->setOnClick('window.lam.treeview.addValue(event, this);');
$valueTable->addElement($addButton); $valueLine->addElement($addButton);
} }
if (!$this->isJpegAttribute($attributeName, $schemaAttribute) || ($value !== '')) { if (!$this->isJpegAttribute($attributeName, $schemaAttribute) || ($value !== '')) {
$clearButton = new htmlLink(null, '#', '../../graphics/del.svg'); $clearButton = new htmlLink(null, '#', '../../graphics/del.svg');
$clearButton->setCSSClasses(array('margin2')); $clearButton->setCSSClasses(array('margin2'));
$clearButton->setOnClick('window.lam.treeview.clearValue(event, this);'); $clearButton->setOnClick('window.lam.treeview.clearValue(event, this);');
$valueTable->addElement($clearButton); $valueLine->addElement($clearButton);
} }
} }
$valueTable->addNewLine(); $valueListContents[] = $valueLine;
} }
return $this->addExtraAttributeContent($valueTable, $attributeName, $schemaAttribute); $listClasses = array('nowrap unstyled-list');
if ($this->isOrderedAttribute($values)) {
$listId = 'attributeList_' . $safeAttributeName;
$valueList = new htmlSortableList($valueListContents, $listId);
$listClasses[] = 'tree-attribute-sorted-list';
$valueList->setOnUpdate('function() {window.lam.treeview.updateAttributePositionData(\'' . $listId . '\');}');
}
else {
$valueList = new htmlList($valueListContents, 'attributeList_' . $safeAttributeName);
}
$valueList->setCSSClasses($listClasses);
return $this->addExtraAttributeContent($valueList, $attributeName, $schemaAttribute);
}
/**
* Returns if the attribute has ordered values.
*
* @param array $values values
* @return bool is ordered
*/
private function isOrderedAttribute(array $values): bool {
if (empty($values)) {
return false;
}
$regex = '/^\\{[0-9]+\\}/';
foreach ($values as $value) {
if (!preg_match($regex, $value)) {
return false;
}
}
return true;
} }
/** /**

View file

@ -604,12 +604,30 @@ input.markPass {
background: white url('../graphics/pass.svg') right no-repeat; background: white url('../graphics/pass.svg') right no-repeat;
} }
.sortableList { list-style-type: none; margin: 0; padding: 0; } .module-list {
.sortableList li { margin: 0 3px 3px 3px; padding: 0.4em; padding-left: 1.5em; } list-style-type: none;
.sortableList li span { margin: 0;
padding: 0;
}
.module-list li {
margin: 0 3px 3px 3px;
padding: 0.4em;
padding-left: 1.5em;
border: 1px solid #c5c5c5;
background: #f6f6f6;
font-weight: normal;
color: #454545;
}
.module-list li span {
position: absolute; position: absolute;
margin-left: -1.3em; margin-left: -1.3em;
margin-top: 0.4rem; margin-top: 0.4rem;
cursor: grab;
}
.unstyled-list {
list-style-type: none;
padding-left: 0;
} }
.strike-through { .strike-through {
@ -864,3 +882,10 @@ div.tree-highlight select {
div.tree-highlight { div.tree-highlight {
font-weight: bold; font-weight: bold;
} }
.tree-attribute-sorted-list li span {
position: absolute;
margin-left: -1.3em;
margin-top: 0.4rem;
cursor: grab;
}

View file

@ -234,9 +234,10 @@ function config_showAccountModules($type, &$container): void {
$el->addElement($delButton); $el->addElement($delButton);
$listElements[] = $el; $listElements[] = $el;
} }
$selSortable = new htmlSortableList($listElements, $type->getId() . '_selected', null); $selSortable = new htmlSortableList($listElements, $type->getId() . '_selected');
$selSortable->alignment = htmlElement::ALIGN_TOP; $selSortable->alignment = htmlElement::ALIGN_TOP;
$selSortable->setOnUpdate('updateModulePositions(\'positions_' . $type->getId() . '\', ui.item.data(\'posOrig\'), ui.item.index());'); $selSortable->setCSSClasses(array('module-list'));
$selSortable->setOnUpdate('function() {updateModulePositions(\'positions_' . $type->getId() . '\', \'' . $type->getId() . '_selected' . '\');}');
$container->add($selSortable, 12, 6); $container->add($selSortable, 12, 6);
} }
else { else {

File diff suppressed because it is too large Load diff

View file

@ -643,27 +643,18 @@ function checkPasswordStrengthHandleReply(data, fieldID) {
* Updates the positions of a htmlSortable list in a hidden input field. * Updates the positions of a htmlSortable list in a hidden input field.
* The positions must be separated by comma (e.g. "0,1,2,3"). * The positions must be separated by comma (e.g. "0,1,2,3").
* *
* @param id HTML ID of hidden input field * @param inputId HTML ID of hidden input field
* @param oldPos old position * @param containerId HTML ID of ul-container
* @param newPos new position
*/ */
function updateModulePositions(id, oldPos, newPos) { function updateModulePositions(inputId, containerId) {
var positions = jQuery('#' + id).val().split(','); const input = document.getElementById(inputId);
if (newPos > oldPos) { let positions = [];
var save = positions[oldPos]; const container = document.getElementById(containerId);
for (var i = oldPos; i < newPos; i++) { const childLiElements = container.children;
positions[i] = positions[i + 1]; for (let i = 0; i < childLiElements.length; i++) {
} positions[i] = childLiElements[i].getAttribute('data-position-orig');
positions[newPos] = save;
} }
if (newPos < oldPos) { input.value = positions.join(',');
var oldPosition = positions[oldPos];
for (var position = oldPos; position > newPos; position--) {
positions[position] = positions[position - 1];
}
positions[newPos] = oldPosition;
}
jQuery('#' + id).val(positions.join(','));
} }
window.lam.filterSelect = window.lam.filterSelect || {}; window.lam.filterSelect = window.lam.filterSelect || {};
@ -2810,6 +2801,20 @@ window.lam.treeview.checkPassword = function(event, element, tokenName, tokenVal
}); });
} }
/**
* Updates the positions of a sorted list of LDAP values.
*
* @param containerId HTML ID of ul-container
*/
window.lam.treeview.updateAttributePositionData = function(containerId) {
const container = document.getElementById(containerId);
const childLiElements = container.children;
for (let i = 0; i < childLiElements.length; i++) {
const inputField = childLiElements[i].querySelector('input');
inputField.value = '{' + i + '}' + inputField.value.replace(/^\{[0-9]+\}/, '');
}
}
window.lam.topmenu = window.lam.topmenu || {}; window.lam.topmenu = window.lam.topmenu || {};
/** /**