-
Côme Chilliet authored
This should avoid future problems related to dots or numbers in action names like the case of ldap2zone. issue #6180
Unverifieda1ab8cf9
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2003-2010 Cajus Pollmeier
Copyright (C) 2011-2018 FusionDirectory
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*!
* \file class_managementListing.inc
* Source code for class managementListing
*/
/*!
* \brief This class handles the entries list for a management instance
*/
class managementListing
{
public $pid;
protected $entries = [];
protected $entriesIndex = [];
protected $base;
protected $sortDirection = NULL;
protected $sortColumn = NULL;
protected $baseMode = TRUE;
protected $multiSelect = TRUE;
protected $bases = [];
protected $header = [];
protected $objectTypeCount = [];
protected $baseSelector;
/* The management class */
public $parent;
protected $columns;
protected $showFooter;
/*!
* \brief Create a listing
*
* \param string $parent management instance
*/
function __construct (management $parent, bool $baseMode = TRUE, bool $multiSelect = TRUE)
{
global $config;
$this->parent = $parent;
$this->baseMode = $baseMode;
$this->multiSelect = $multiSelect;
// Initialize pid
$this->pid = preg_replace('/[^0-9]/', '', microtime(TRUE));
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
$this->setUpBaseSelector();
// Move footer information
$this->showFooter = ($config->get_cfg_value('listSummary') == 'TRUE');
$this->reloadColumns();
}
function setUpBaseSelector ()
{
global $config, $ui;
// Set base for filter
if ($this->baseMode) {
$this->base = $ui->getCurrentBase();
$this->refreshBasesList();
// Instanciate base selector
$this->baseSelector = new baseSelector($this->bases, $this->base);
} else {
$this->base = $config->current['BASE'];
}
}
function getBaseMode (): bool
{
return $this->baseMode;
}
function getMultiSelect (): bool
{
return $this->multiSelect;
}
function reloadColumns ()
{
$columnInfos = $this->parent->getColumnConfiguration();
$this->columns = [];
$first = TRUE;
foreach ($columnInfos as $columnInfo) {
$column = Column::build($this, $columnInfo[0], $columnInfo[1]);
if ($first && !empty($columnInfo[1]['attributes'])) {
$column->setTemplateAttributes(['cn']);
$first = FALSE;
}
$this->columns[] = $column;
}
}
function getColumns (): array
{
return $this->columns;
}
function renderHeader ()
{
$this->header = [];
// Initialize sort?
$sortInit = FALSE;
if (!isset($this->sortDirection)) {
$this->sortColumn = 0;
$this->sortDirection = [];
$sortInit = TRUE;
}
foreach ($this->columns as $index => $column) {
// Initialize everything to one direction
if ($sortInit) {
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
$this->sortDirection[$index] = FALSE;
}
$this->header[$index] = [
'props' => $column->getHtmlProps(),
'sortable' => $column->isSortable(),
'label' => $column->getLabel(),
];
if ($index == $this->sortColumn) {
if ($column->isSortable()) {
$this->header[$index]['sortdirection'] = $this->sortDirection[$index];
} elseif ($sortInit) {
/* sortColumn is not sortable, try next one */
$this->sortColumn++;
}
}
}
}
/*!
* \brief Render
*/
function render (): string
{
global $ui;
// Check for exeeded sizelimit
if (($message = $ui->getSizeLimitHandler()->check()) != '') {
return $message;
}
$this->renderHeader();
$smarty = get_smarty();
$smarty->assign('PID', $this->pid);
$smarty->assign('PLUG', $_GET['plug']);
$smarty->assign('multiSelect', $this->multiSelect);
$smarty->assign('showFooter', $this->showFooter);
$smarty->assign('headers', $this->header);
$smarty->assign('columnCount', (count($this->columns) + ($this->multiSelect ? 1 : 0)));
// Complete list by sorting entries and appending them to the output
$entryIterator = $this->getIterator();
$rows = [];
foreach ($entryIterator as $entry) {
$row = [
'cells' => [],
'classes' => [],
];
foreach ($this->columns as $column) {
$row['cells'][] = [
'props' => $column->getHtmlCellProps(),
'render' => $column->renderCell($entry)
];
$column->fillRowClasses($row['classes'], $entry);
}
$row['index'] = $entry->row;
$rows[] = $row;
}
$smarty->assign('rows', $rows);
// Add the footer if requested
if ($this->showFooter) {
$types = [];
foreach ($this->parent->objectTypes as $type) {
if (isset($this->objectTypeCount[$type])) {
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
$infos = objects::infos($type);
$types[] = [
'name' => $infos['name'],
'icon' => $infos['icon'],
'count' => $this->objectTypeCount[$type],
];
}
if (isset($this->objectTypeCount['template_'.$type])) {
$infos = objects::infos($type);
$types[] = [
'name' => sprintf(_('%s template'), $infos['name']),
'icon' => 'geticon.php?context=devices&icon=template&size=16',
'count' => $this->objectTypeCount['template_'.$type],
];
}
}
$smarty->assign('objectCounts', $types);
}
/* If the user ignored the sizelimit warning he may get more entries than what PHP can handle */
$error = ldapSizeLimit::checkMaxInputVars(
count($this->entries),
_('The number of listed entries (%d) is greater than or too close to configured max_input_vars PHP ini setting (%d). Please change max_input_vars ini setting to a higher value.')
);
if ($error !== FALSE) {
$error->display();
}
return $smarty->fetch(get_template_path('management/list.tpl'));
}
function getIterator (): Iterator
{
return new EntrySortIterator($this->entries, $this->columns[$this->sortColumn] ?? NULL, $this->sortDirection[$this->sortColumn] ?? FALSE);
}
/*!
* \brief Updates base and sorting according to POST and GET infos
*/
function updateBase ()
{
global $ui;
// Take care of base selector
if ($this->baseMode) {
$this->baseSelector->update();
// Check if a wrong base was supplied
if (!$this->baseSelector->checkLastBaseUpdate()) {
$error = new FusionDirectoryError(msgPool::check_base());
$error->display();
}
// Save base
$this->base = $this->baseSelector->getBase();
$ui->setCurrentBase($this->base);
}
// Do not do anything if this is not our PID
if ($this->baseMode || !(isset($_REQUEST['PID']) && ($_REQUEST['PID'] != $this->pid))) {
// Filter GET with "act" attributes
if (isset($_GET['act'])) {
$key = validate($_GET['act']);
if (preg_match('/^SORT_([0-9]+)$/', $key, $match)) {
$this->setSortColumn($match[1]);
// Allow header to update itself according to the new sort settings
$this->renderHeader();
}
}
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
if ($this->baseMode) {
// Override base if we got signals from the navigation elements
$action = '';
foreach (array_keys($_POST) as $key) {
if (preg_match('/^(ROOT|BACK|HOME)_x$/', $key, $match)) {
$action = $match[1];
break;
}
}
// Navigation handling
if ($action == 'ROOT') {
$this->setBase(key($this->bases));
} elseif ($action == 'BACK') {
$parentBase = preg_replace('/^[^,]+,/', '', $this->base);
$this->tryAndSetBase($parentBase);
} elseif ($action == 'HOME') {
$ui = get_userinfo();
$this->tryAndSetBase($ui->getBase());
}
}
}
}
/*!
* \brief Update a listing
*/
function update (string $dn = NULL)
{
if ($dn === NULL) {
$this->updateBase();
}
// Update filter
$this->parent->filter->update($this->base);
// Update filter and refresh entries
$attrs = $this->parent->neededAttrs;
foreach ($this->columns as $column) {
$column->fillNeededAttributes($attrs);
}
if ($dn !== NULL) {
$this->parent->filter->setScope('base');
list($this->entries, $this->objectTypeCount) = $this->parent->filter->query($attrs, $dn);
$this->parent->filter->setScope('one');
} else {
list($this->entries, $this->objectTypeCount) = $this->parent->filter->query($attrs, $this->base);
}
/* Store the order of the entries to access them by index later */
$this->entriesIndex = array_keys($this->entries);
}
/*!
* \brief Set a new base valor
*
* \param string $base
*/
function setBase (string $base)
{
global $ui;
$this->base = $base;
if ($this->baseMode) {
$this->baseSelector->setBase($this->base);
$ui->setCurrentBase($this->base);
}
}
function tryAndSetBase ($base)
{
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
if (isset($this->bases[$base])) {
$this->setBase($base);
}
}
/*!
* \brief Accessor of the base
*
* \return the base
*/
function getBase (): string
{
return $this->base;
}
function renderBase (): string
{
if (!$this->baseMode) {
return '';
}
return $this->baseSelector->render();
}
function renderNavigation (bool $skipConfiguration = FALSE): array
{
global $ui;
if ($this->baseMode) {
$enableBack = TRUE;
$enableRoot = TRUE;
$enableHome = TRUE;
/* Check if base = first available base */
$deps = array_keys($this->bases);
if (!count($deps) || $deps[0] == $this->base) {
$enableBack = FALSE;
$enableRoot = FALSE;
}
/* Check if we are in users home department */
if (!count($deps) || ($this->base == $ui->getBase()) || !in_array_ics($ui->getBase(), $deps)) {
$enableHome = FALSE;
}
$actions = [
[
'id' => 'ROOT',
'desc' => _('Go to root department'),
'name' => _('Root'),
'icon' => 'geticon.php?context=actions&icon=go-first&size=16',
'enabled' => $enableRoot,
'class' => '',
],
[
'id' => 'BACK',
'desc' => _('Go up one department'),
'name' => _('Up'),
'icon' => 'geticon.php?context=actions&icon=go-up&size=16',
'enabled' => $enableBack,
'class' => '',
],
[
'id' => 'HOME',
'desc' => _('Go to user\'s department'),
'name' => _('Home'),
'icon' => 'geticon.php?context=actions&icon=go-home&size=16',
'enabled' => $enableHome,
'class' => '',
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
],
];
} else {
$actions = [];
}
$actions[] = [
'id' => 'REFRESH',
'desc' => _('Reload list'),
'name' => _('Reload'),
'icon' => 'geticon.php?context=actions&icon=view-refresh&size=16',
'enabled' => TRUE,
'class' => 'optional',
];
if (!$skipConfiguration) {
$actions[] = [
'id' => 'listing_configure',
'desc' => _('Configure this management list'),
'name' => _('Configure'),
'icon' => 'geticon.php?context=categories&icon=settings&size=16',
'enabled' => TRUE,
'class' => '',
];
}
return $actions;
}
/*!
* \brief Get action
*/
function getAction (): array
{
global $config;
$result = ['targets' => [], 'action' => '', 'subaction' => NULL];
// Do not do anything if this is not our PID, or there's even no PID available...
if (!isset($_REQUEST['dn']) && (!isset($_REQUEST['PID']) || $_REQUEST['PID'] != $this->pid)) {
return $result;
}
if (isset($_GET['act'])) {
// Filter GET with "act" attributes
$key = validate($_GET['act']);
if (preg_match('/^listing_([[:alnum:]_\.]+)_([0-9]+)$/', $key, $m)) {
$target = $m[2];
if (isset($this->entriesIndex[$target])) {
$result['action'] = $m[1];
$result['targets'][] = $this->entriesIndex[$target];
}
} elseif (isset($_REQUEST['dn']) && preg_match('/^listing_([[:alnum:]_\.]+)$/', $key, $m)) {
/* Pre-render list to init things if a dn is gonna be opened on first load */
$dn = urldecode($_REQUEST['dn']);
$this->focusDn($dn);
$this->render();
$result['action'] = $m[1];
$result['targets'][] = $dn;
// Make sure no other management class intercept the same dn
unset($_REQUEST['dn']);
}
} else {
// Filter POST with "act" attributes -> posted from action menu
if (isset($_POST['act']) && ($_POST['act'] != '')) {
$result['action'] = validate($_POST['act']);
}
// Filter POST with "listing_" attributes
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
foreach (array_keys($_POST) as $key) {
// Capture selections
if (preg_match('/^listing_selected_([0-9]+)$/', $key, $m)) {
$target = $m[1];
if (isset($this->entriesIndex[$target])) {
$result['targets'][] = $this->entriesIndex[$target];
}
continue;
}
// Capture action with target - this is a one shot
if (preg_match('/^listing_([[:alnum:]_\.]+)_([0-9]+)(|_x)$/', $key, $m)) {
$target = $m[2];
if (isset($this->entriesIndex[$target])) {
$result['action'] = $m[1];
$result['targets'] = [$this->entriesIndex[$target]];
}
break;
}
// Capture action without target
if (preg_match('/^listing_([[:alnum:]_\.]+)(|_x)$/', $key, $m)) {
$result['action'] = $m[1];
continue;
}
}
}
if (strpos($result['action'], '_') !== FALSE) {
list($result['action'], $result['subaction']) = explode('_', $result['action'], 2);
}
return $result;
}
/*!
* \brief Set base close to this dn and load only him
*/
function focusDn (string $dn)
{
/* Detect the longer base valid for this dn */
$longerBase = '';
foreach (array_keys($this->bases) as $base) {
if (preg_match('/'.preg_quote($base, '/').'$/i', $dn)
&& (strlen($base) > strlen($longerBase))) {
$longerBase = $base;
}
}
$this->setBase($longerBase);
$this->update($dn);
}
/*!
* \brief Refresh the bases list
*/
function refreshBasesList ()
{
global $config;
$ui = get_userinfo();
// Fill internal bases list
$this->bases = [];
$categories = [];
foreach ($this->parent->objectTypes as $otype) {
$i = objects::infos($otype);
$categories[$i['aclCategory']] = $i['aclCategory'];
}
$deps = $ui->get_module_departments(array_values($categories));
$departmentTree = $config->getDepartmentTree();
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
foreach ($departmentTree as $key => $dep) {
if (in_array_ics($key, $deps)) {
$this->bases[$key] = $dep;
}
}
if (!empty($this->bases) && !isset($this->bases[$this->base])) {
$this->base = key($this->bases);
}
// Populate base selector if already present
if ($this->baseSelector && $this->baseMode) {
$this->baseSelector->setBases($this->bases);
$this->baseSelector->setBase($this->base);
$this->baseSelector->update(TRUE);
}
}
/*!
* \brief Get entry
*
* \param string $dn The DN
*/
function getEntry (string $dn)
{
if (isset($this->entries[$dn])) {
return $this->entries[$dn];
}
return NULL;
}
/*!
* \brief Set sort column
*
* \param int $column Index of column to sort by
* \param bool $direction Whether to sort up or down
*/
public function setSortColumn (int $column, bool $direction = NULL)
{
if ($direction === NULL) {
// Switch to new column or invert search order?
$direction = (($this->sortColumn == $column) && !$this->sortDirection[$column]);
}
$this->sortColumn = $column;
$this->sortDirection[$column] = $direction;
}
function fillSearchedAttributes (string $type, array &$attrs)
{
global $ui;
$searchedAttributes = [];
foreach ($this->columns as $column) {
$column->fillSearchedAttributes($searchedAttributes);
}
$searchedAttributes = array_unique($searchedAttributes);
foreach ($searchedAttributes as $attr) {
if (!isset($attrs[$attr])) {
$category = $ui->getAttributeCategory($type, $attr);
if ($category !== FALSE) {
$attrs[$attr] = $category;
}
}
}
}
}