Файл: contao-3.5.8/system/modules/core/drivers/DC_Table.php
Строк: 3894
<?php
/**
* Contao Open Source CMS
*
* Copyright (c) 2005-2016 Leo Feyer
*
* @license LGPL-3.0+
*/
namespace Contao;
/**
* Provide methods to modify the database.
*
* @author Leo Feyer <https://github.com/leofeyer>
* @author Andreas Schempp <https://github.com/aschempp>
*/
class DC_Table extends DataContainer implements listable, editable
{
/**
* Name of the parent table
* @var string
*/
protected $ptable;
/**
* Names of the child tables
* @var array
*/
protected $ctable;
/**
* IDs of all root records
* @var array
*/
protected $root;
/**
* ID of the button container
* @var string
*/
protected $bid;
/**
* Limit (database query)
* @var string
*/
protected $limit;
/**
* Total (database query)
* @var string
*/
protected $total;
/**
* First sorting field
* @var string
*/
protected $firstOrderBy;
/**
* Order by (database query)
* @var array
*/
protected $orderBy = array();
/**
* Fields of a new or duplicated record
* @var array
*/
protected $set = array();
/**
* IDs of all records that are currently displayed
* @var array
*/
protected $current = array();
/**
* Show the current table as tree
* @var boolean
*/
protected $treeView = false;
/**
* True if a new version has to be created
* @var boolean
*/
protected $blnCreateNewVersion = false;
/**
* True if one of the form fields is uploadable
* @var boolean
*/
protected $blnUploadable = false;
/**
* The current back end module
* @var array
*/
protected $arrModule = array();
/**
* Initialize the object
*
* @param string $strTable
* @param array $arrModule
*/
public function __construct($strTable, $arrModule=array())
{
parent::__construct();
// Check the request token (see #4007)
if (isset($_GET['act']))
{
if (!isset($_GET['rt']) || !RequestToken::validate(Input::get('rt')))
{
$this->Session->set('INVALID_TOKEN_URL', Environment::get('request'));
$this->redirect('contao/confirm.php');
}
}
$this->intId = Input::get('id');
// Clear the clipboard
if (isset($_GET['clipboard']))
{
$this->Session->set('CLIPBOARD', array());
$this->redirect($this->getReferer());
}
// Check whether the table is defined
if ($strTable == '' || !isset($GLOBALS['TL_DCA'][$strTable]))
{
$this->log('Could not load the data container configuration for "' . $strTable . '"', __METHOD__, TL_ERROR);
trigger_error('Could not load the data container configuration', E_USER_ERROR);
}
// Set IDs and redirect
if (Input::post('FORM_SUBMIT') == 'tl_select')
{
$ids = Input::post('IDS');
if (empty($ids) || !is_array($ids))
{
$this->reload();
}
$session = $this->Session->getData();
$session['CURRENT']['IDS'] = $ids;
$this->Session->setData($session);
if (isset($_POST['edit']))
{
$this->redirect(str_replace('act=select', 'act=editAll', Environment::get('request')));
}
elseif (isset($_POST['delete']))
{
$this->redirect(str_replace('act=select', 'act=deleteAll', Environment::get('request')));
}
elseif (isset($_POST['override']))
{
$this->redirect(str_replace('act=select', 'act=overrideAll', Environment::get('request')));
}
elseif (isset($_POST['cut']) || isset($_POST['copy']))
{
$arrClipboard = $this->Session->get('CLIPBOARD');
$arrClipboard[$strTable] = array
(
'id' => $ids,
'mode' => (isset($_POST['cut']) ? 'cutAll' : 'copyAll')
);
$this->Session->set('CLIPBOARD', $arrClipboard);
// Support copyAll in the list view (see #7499)
if (isset($_POST['copy']) && $GLOBALS['TL_DCA'][$strTable]['list']['sorting']['mode'] < 4)
{
$this->redirect(str_replace('act=select', 'act=copyAll', Environment::get('request')));
}
$this->redirect($this->getReferer());
}
}
$this->strTable = $strTable;
$this->ptable = $GLOBALS['TL_DCA'][$this->strTable]['config']['ptable'];
$this->ctable = $GLOBALS['TL_DCA'][$this->strTable]['config']['ctable'];
$this->treeView = in_array($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'], array(5, 6));
$this->root = null;
$this->arrModule = $arrModule;
// Call onload_callback (e.g. to check permissions)
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['config']['onload_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['config']['onload_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($this);
}
elseif (is_callable($callback))
{
$callback($this);
}
}
}
// Get the IDs of all root records (tree view)
if ($this->treeView)
{
$table = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 6) ? $this->ptable : $this->strTable;
// Unless there are any root records specified, use all records with parent ID 0
if (!isset($GLOBALS['TL_DCA'][$table]['list']['sorting']['root']) || $GLOBALS['TL_DCA'][$table]['list']['sorting']['root'] === false)
{
$objIds = $this->Database->prepare("SELECT id FROM " . $table . " WHERE pid=?" . ($this->Database->fieldExists('sorting', $table) ? ' ORDER BY sorting' : ''))
->execute(0);
if ($objIds->numRows > 0)
{
$this->root = $objIds->fetchEach('id');
}
}
// Get root records from global configuration file
elseif (is_array($GLOBALS['TL_DCA'][$table]['list']['sorting']['root']))
{
$this->root = $this->eliminateNestedPages($GLOBALS['TL_DCA'][$table]['list']['sorting']['root'], $table, $this->Database->fieldExists('sorting', $table));
}
}
// Get the IDs of all root records (list view or parent view)
elseif (is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['root']))
{
$this->root = array_unique($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['root']);
}
// Store the current referer
if (!empty($this->ctable) && !Input::get('act') && !Input::get('key') && !Input::get('token') && TL_SCRIPT == 'contao/main.php' && !Environment::get('isAjaxRequest'))
{
$session = $this->Session->get('referer');
$session[TL_REFERER_ID][$this->strTable] = substr(Environment::get('requestUri'), strlen(TL_PATH) + 1);
$this->Session->set('referer', $session);
}
}
/**
* Set an object property
*
* @param string $strKey
* @param mixed $varValue
*/
public function __set($strKey, $varValue)
{
switch ($strKey)
{
case 'createNewVersion':
$this->blnCreateNewVersion = (bool) $varValue;
break;
default;
parent::__set($strKey, $varValue);
break;
}
}
/**
* Return an object property
*
* @param string $strKey
*
* @return mixed
*/
public function __get($strKey)
{
switch ($strKey)
{
case 'id':
return $this->intId;
break;
case 'parentTable':
return $this->ptable;
break;
case 'childTable':
return $this->ctable;
break;
case 'rootIds':
return $this->root;
break;
case 'createNewVersion':
return $this->blnCreateNewVersion;
break;
}
return parent::__get($strKey);
}
/**
* List all records of a particular table
*
* @return string
*/
public function showAll()
{
$return = '';
$this->limit = '';
$this->bid = 'tl_buttons';
// Clean up old tl_undo and tl_log entries
if ($this->strTable == 'tl_undo' && strlen(Config::get('undoPeriod')))
{
$this->Database->prepare("DELETE FROM tl_undo WHERE tstamp<?")
->execute(intval(time() - Config::get('undoPeriod')));
}
elseif ($this->strTable == 'tl_log' && strlen(Config::get('logPeriod')))
{
$this->Database->prepare("DELETE FROM tl_log WHERE tstamp<?")
->execute(intval(time() - Config::get('logPeriod')));
}
$this->reviseTable();
// Add to clipboard
if (Input::get('act') == 'paste')
{
$arrClipboard = $this->Session->get('CLIPBOARD');
$arrClipboard[$this->strTable] = array
(
'id' => Input::get('id'),
'childs' => Input::get('childs'),
'mode' => Input::get('mode')
);
$this->Session->set('CLIPBOARD', $arrClipboard);
}
// Custom filter
if (!empty($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['filter']) && is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['filter']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['filter'] as $filter)
{
$this->procedure[] = $filter[0];
$this->values[] = $filter[1];
}
}
// Render view
if ($this->treeView)
{
$return .= $this->panel();
$return .= $this->treeView();
}
else
{
if (Input::get('table') && $this->ptable && $this->Database->fieldExists('pid', $this->strTable))
{
$this->procedure[] = 'pid=?';
$this->values[] = CURRENT_ID;
}
$return .= $this->panel();
$return .= ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 4) ? $this->parentView() : $this->listView();
// Add another panel at the end of the page
if (strpos($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['panelLayout'], 'limit') !== false)
{
$return .= $this->paginationMenu();
}
}
// Store the current IDs
$session = $this->Session->getData();
$session['CURRENT']['IDS'] = $this->current;
$this->Session->setData($session);
return $return;
}
/**
* Return all non-excluded fields of a record as HTML table
*
* @return string
*/
public function show()
{
if (!strlen($this->intId))
{
return '';
}
$objRow = $this->Database->prepare("SELECT * FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($this->intId);
if ($objRow->numRows < 1)
{
return '';
}
$count = 1;
$return = '';
$row = $objRow->row();
// Get the order fields
$objDcaExtractor = DcaExtractor::getInstance($this->strTable);
$arrOrder = $objDcaExtractor->getOrderFields();
// Get all fields
$fields = array_keys($row);
$allowedFields = array('id', 'pid', 'sorting', 'tstamp');
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['fields']))
{
$allowedFields = array_unique(array_merge($allowedFields, array_keys($GLOBALS['TL_DCA'][$this->strTable]['fields'])));
}
// Use the field order of the DCA file
$fields = array_intersect($allowedFields, $fields);
// Show all allowed fields
foreach ($fields as $i)
{
if (!in_array($i, $allowedFields) || $GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['inputType'] == 'password' || $GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['eval']['doNotShow'] || $GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['eval']['hideInput'])
{
continue;
}
// Special treatment for table tl_undo
if ($this->strTable == 'tl_undo' && $i == 'data')
{
continue;
}
$value = deserialize($row[$i]);
// Decrypt the value
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['eval']['encrypt'])
{
$value = Encryption::decrypt($value);
}
$class = (($count++ % 2) == 0) ? ' class="tl_bg"' : '';
// Get the field value
if (isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['foreignKey']))
{
$temp = array();
$chunks = explode('.', $GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['foreignKey'], 2);
foreach ((array) $value as $v)
{
$objKey = $this->Database->prepare("SELECT " . $chunks[1] . " AS value FROM " . $chunks[0] . " WHERE id=?")
->limit(1)
->execute($v);
if ($objKey->numRows)
{
$temp[] = $objKey->value;
}
}
$row[$i] = implode(', ', $temp);
}
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['inputType'] == 'fileTree' || in_array($i, $arrOrder))
{
if (is_array($value))
{
foreach ($value as $kk=>$vv)
{
$value[$kk] = $vv ? StringUtil::binToUuid($vv) : '';
}
$row[$i] = implode(', ', $value);
}
else
{
$row[$i] = $value ? StringUtil::binToUuid($value) : '';
}
}
elseif (is_array($value))
{
foreach ($value as $kk=>$vv)
{
if (is_array($vv))
{
$vals = array_values($vv);
$value[$kk] = $vals[0].' ('.$vals[1].')';
}
}
$row[$i] = implode(', ', $value);
}
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['eval']['rgxp'] == 'date')
{
$row[$i] = $value ? Date::parse(Config::get('dateFormat'), $value) : '-';
}
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['eval']['rgxp'] == 'time')
{
$row[$i] = $value ? Date::parse(Config::get('timeFormat'), $value) : '-';
}
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['eval']['rgxp'] == 'datim' || in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['flag'], array(5, 6, 7, 8, 9, 10)) || $i == 'tstamp')
{
$row[$i] = $value ? Date::parse(Config::get('datimFormat'), $value) : '-';
}
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['inputType'] == 'checkbox' && !$GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['eval']['multiple'])
{
$row[$i] = ($value != '') ? $GLOBALS['TL_LANG']['MSC']['yes'] : $GLOBALS['TL_LANG']['MSC']['no'];
}
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['inputType'] == 'textarea' && ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['eval']['allowHtml'] || $GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['eval']['preserveTags']))
{
$row[$i] = specialchars($value);
}
elseif (is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['reference']))
{
$row[$i] = isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['reference'][$row[$i]]) ? ((is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['reference'][$row[$i]])) ? $GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['reference'][$row[$i]][0] : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['reference'][$row[$i]]) : $row[$i];
}
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['eval']['isAssociative'] || array_is_assoc($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['options']))
{
$row[$i] = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['options'][$row[$i]];
}
else
{
$row[$i] = $value;
}
// Label
if (isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['label']))
{
$label = is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['label']) ? $GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['label'][0] : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$i]['label'];
}
else
{
$label = is_array($GLOBALS['TL_LANG']['MSC'][$i]) ? $GLOBALS['TL_LANG']['MSC'][$i][0] : $GLOBALS['TL_LANG']['MSC'][$i];
}
if ($label == '')
{
$label = $i;
}
$return .= '
<tr>
<td'.$class.'><span class="tl_label">'.$label.': </span></td>
<td'.$class.'>'.$row[$i].'</td>
</tr>';
}
// Special treatment for tl_undo
if ($this->strTable == 'tl_undo')
{
$arrData = deserialize($objRow->data);
foreach ($arrData as $strTable=>$arrTableData)
{
System::loadLanguageFile($strTable);
$this->loadDataContainer($strTable);
foreach ($arrTableData as $arrRow)
{
$count = 0;
$return .= '
<tr>
<td colspan="2" style="padding:0"><div style="margin-bottom:26px;line-height:24px;border-bottom:1px dotted #ccc"> </div></td>
</tr>';
foreach ($arrRow as $i=>$v)
{
if (is_array(deserialize($v)))
{
continue;
}
$class = (($count++ % 2) == 0) ? ' class="tl_bg"' : '';
// Get the field label
if (isset($GLOBALS['TL_DCA'][$strTable]['fields'][$i]['label']))
{
$label = is_array($GLOBALS['TL_DCA'][$strTable]['fields'][$i]['label']) ? $GLOBALS['TL_DCA'][$strTable]['fields'][$i]['label'][0] : $GLOBALS['TL_DCA'][$strTable]['fields'][$i]['label'];
}
else
{
$label = is_array($GLOBALS['TL_LANG']['MSC'][$i]) ? $GLOBALS['TL_LANG']['MSC'][$i][0] : $GLOBALS['TL_LANG']['MSC'][$i];
}
if (!strlen($label))
{
$label = $i;
}
// Always encode special characters (thanks to Oliver Klee)
$return .= '
<tr>
<td'.$class.'><span class="tl_label">'.$label.': </span></td>
<td'.$class.'>'.specialchars($v).'</td>
</tr>';
}
}
}
}
// Return table
return '
<div id="tl_buttons">' . (!Input::get('popup') ? '
<a href="'.$this->getReferer(true).'" class="header_back" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['backBTTitle']).'" accesskey="b" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG']['MSC']['backBT'].'</a>' : '') . '
</div>
<table class="tl_show">'.$return.'
</table>';
}
/**
* Insert a new row into a database table
*
* @param array $set
*/
public function create($set=array())
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['notCreatable'])
{
$this->log('Table "'.$this->strTable.'" is not creatable', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
// Get all default values for the new entry
foreach ($GLOBALS['TL_DCA'][$this->strTable]['fields'] as $k=>$v)
{
// Use array_key_exists here (see #5252)
if (array_key_exists('default', $v))
{
$this->set[$k] = is_array($v['default']) ? serialize($v['default']) : $v['default'];
// Encrypt the default value (see #3740)
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]['eval']['encrypt'])
{
$this->set[$k] = Encryption::encrypt($this->set[$k]);
}
}
}
// Set passed values
if (!empty($set) && is_array($set))
{
$this->set = array_merge($this->set, $set);
}
// Get the new position
$this->getNewPosition('new', (strlen(Input::get('pid')) ? Input::get('pid') : null), (Input::get('mode') == '2' ? true : false));
// Dynamically set the parent table of tl_content
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['dynamicPtable'])
{
$this->set['ptable'] = $this->ptable;
}
// Empty the clipboard
$arrClipboard = $this->Session->get('CLIPBOARD');
$arrClipboard[$this->strTable] = array();
$this->Session->set('CLIPBOARD', $arrClipboard);
// Insert the record if the table is not closed and switch to edit mode
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['closed'])
{
$this->set['tstamp'] = 0;
$objInsertStmt = $this->Database->prepare("INSERT INTO " . $this->strTable . " %s")
->set($this->set)
->execute();
if ($objInsertStmt->affectedRows)
{
$s2e = $GLOBALS['TL_DCA'][$this->strTable]['config']['switchToEdit'] ? '&s2e=1' : '';
$insertID = $objInsertStmt->insertId;
// Save new record in the session
$new_records = $this->Session->get('new_records');
$new_records[$this->strTable][] = $insertID;
$this->Session->set('new_records', $new_records);
// Call the oncreate_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['config']['oncreate_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['config']['oncreate_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($this->strTable, $insertID, $this->set, $this);
}
elseif (is_callable($callback))
{
$callback($this->strTable, $insertID, $this->set, $this);
}
}
}
// Add a log entry
$this->log('A new entry "'.$this->strTable.'.id='.$insertID.'" has been created'.$this->getParentEntries($this->strTable, $insertID), __METHOD__, TL_GENERAL);
$this->redirect($this->switchToEdit($insertID).$s2e);
}
}
$this->redirect($this->getReferer());
}
/**
* Assign a new position to an existing record
*
* @param boolean $blnDoNotRedirect
*/
public function cut($blnDoNotRedirect=false)
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['notSortable'])
{
$this->log('Table "'.$this->strTable.'" is not sortable', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
$cr = array();
// ID and PID are mandatory
if (!$this->intId || !strlen(Input::get('pid')))
{
$this->redirect($this->getReferer());
}
// Get the new position
$this->getNewPosition('cut', Input::get('pid'), (Input::get('mode') == '2' ? true : false));
// Avoid circular references when there is no parent table
if ($this->Database->fieldExists('pid', $this->strTable) && !strlen($this->ptable))
{
$cr = $this->Database->getChildRecords($this->intId, $this->strTable);
$cr[] = $this->intId;
}
// Empty clipboard
$arrClipboard = $this->Session->get('CLIPBOARD');
$arrClipboard[$this->strTable] = array();
$this->Session->set('CLIPBOARD', $arrClipboard);
// Update the record
if (in_array($this->set['pid'], $cr))
{
$this->log('Attempt to relate record '.$this->intId.' of table "'.$this->strTable.'" to its child record '.Input::get('pid').' (circular reference)', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
$this->set['tstamp'] = time();
// HOOK: style sheet category
if ($this->strTable == 'tl_style')
{
$filter = $this->Session->get('filter');
$category = $filter['tl_style_' . CURRENT_ID]['category'];
if ($category != '')
{
$this->set['category'] = $category;
}
}
// Dynamically set the parent table of tl_content
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['dynamicPtable'])
{
$this->set['ptable'] = $this->ptable;
}
$this->Database->prepare("UPDATE " . $this->strTable . " %s WHERE id=?")
->set($this->set)
->execute($this->intId);
// Call the oncut_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['config']['oncut_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['config']['oncut_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($this);
}
elseif (is_callable($callback))
{
$callback($this);
}
}
}
if (!$blnDoNotRedirect)
{
$this->redirect($this->getReferer());
}
}
/**
* Move all selected records
*/
public function cutAll()
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['notSortable'])
{
$this->log('Table "'.$this->strTable.'" is not sortable', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
$arrClipboard = $this->Session->get('CLIPBOARD');
if (isset($arrClipboard[$this->strTable]) && is_array($arrClipboard[$this->strTable]['id']))
{
foreach ($arrClipboard[$this->strTable]['id'] as $id)
{
$this->intId = $id;
$this->cut(true);
Input::setGet('pid', $id);
Input::setGet('mode', 1);
}
}
$this->redirect($this->getReferer());
}
/**
* Duplicate a particular record of the current table
*
* @param boolean $blnDoNotRedirect
*
* @return integer|boolean
*/
public function copy($blnDoNotRedirect=false)
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['notCopyable'])
{
$this->log('Table "'.$this->strTable.'" is not copyable', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
if (!$this->intId)
{
$this->redirect($this->getReferer());
}
$objRow = $this->Database->prepare("SELECT * FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($this->intId);
// Copy the values if the record contains data
if ($objRow->numRows)
{
foreach ($objRow->row() as $k=>$v)
{
if (in_array($k, array_keys($GLOBALS['TL_DCA'][$this->strTable]['fields'])))
{
// Never copy passwords
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]['inputType'] == 'password')
{
$v = Widget::getEmptyValueByFieldType($GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]['sql']);
}
// Empty unique fields or add a unique identifier in copyAll mode
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]['eval']['unique'])
{
$v = (Input::get('act') == 'copyAll') ? $v .'-'. substr(md5(uniqid(mt_rand(), true)), 0, 8) : Widget::getEmptyValueByFieldType($GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]['sql']);
}
// Reset doNotCopy and fallback fields to their default value
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]['eval']['doNotCopy'] || $GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]['eval']['fallback'])
{
$v = '';
// Use array_key_exists to allow NULL (see #5252)
if (array_key_exists('default', $GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]))
{
$v = is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]['default']) ? serialize($GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]['default']) : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]['default'];
}
// Encrypt the default value (see #3740)
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$k]['eval']['encrypt'])
{
$v = Encryption::encrypt($v);
}
}
$this->set[$k] = $v;
}
}
// HOOK: style sheet category
if ($this->strTable == 'tl_style')
{
$filter = $this->Session->get('filter');
$category = $filter['tl_style_' . CURRENT_ID]['category'];
if ($category != '')
{
$this->set['category'] = $category;
}
}
}
// Get the new position
$this->getNewPosition('copy', (strlen(Input::get('pid')) ? Input::get('pid') : null), (Input::get('mode') == '2' ? true : false));
// Dynamically set the parent table of tl_content
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['dynamicPtable'])
{
$this->set['ptable'] = $this->ptable;
}
// Empty clipboard
$arrClipboard = $this->Session->get('CLIPBOARD');
$arrClipboard[$this->strTable] = array();
$this->Session->set('CLIPBOARD', $arrClipboard);
// Insert the record if the table is not closed and switch to edit mode
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['closed'])
{
$this->set['tstamp'] = ($blnDoNotRedirect ? time() : 0);
// Mark the new record with "copy of" (see #2938)
if (isset($GLOBALS['TL_DCA'][$this->strTable]['fields']['name']))
{
if ($this->set['name'] != '')
{
$this->set['name'] = sprintf($GLOBALS['TL_LANG']['MSC']['copyOf'], $this->set['name']);
}
}
elseif (isset($GLOBALS['TL_DCA'][$this->strTable]['fields']['title']))
{
if ($this->set['title'] != '')
{
$this->set['title'] = sprintf($GLOBALS['TL_LANG']['MSC']['copyOf'], $this->set['title']);
}
}
elseif (isset($GLOBALS['TL_DCA'][$this->strTable]['fields']['headline']))
{
$headline = deserialize($this->set['headline']);
if (!is_array($headline))
{
if ($this->set['headline'] != '')
{
$this->set['headline'] = sprintf($GLOBALS['TL_LANG']['MSC']['copyOf'], $this->set['headline']);
}
}
}
// Remove the ID field from the data array
unset($this->set['id']);
$objInsertStmt = $this->Database->prepare("INSERT INTO " . $this->strTable . " %s")
->set($this->set)
->execute();
if ($objInsertStmt->affectedRows)
{
$insertID = $objInsertStmt->insertId;
// Save the new record in the session
if (!$blnDoNotRedirect)
{
$new_records = $this->Session->get('new_records');
$new_records[$this->strTable][] = $insertID;
$this->Session->set('new_records', $new_records);
}
// Duplicate the records of the child table
$this->copyChilds($this->strTable, $insertID, $this->intId, $insertID);
// Call the oncopy_callback after all new records have been created
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['config']['oncopy_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['config']['oncopy_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($insertID, $this);
}
elseif (is_callable($callback))
{
$callback($insertID, $this);
}
}
}
// Add a log entry
$this->log('A new entry "'.$this->strTable.'.id='.$insertID.'" has been created by duplicating record "'.$this->strTable.'.id='.$this->intId.'"'.$this->getParentEntries($this->strTable, $insertID), __METHOD__, TL_GENERAL);
// Switch to edit mode
if (!$blnDoNotRedirect)
{
$this->redirect($this->switchToEdit($insertID));
}
return $insertID;
}
}
if (!$blnDoNotRedirect)
{
$this->redirect($this->getReferer());
}
return false;
}
/**
* Duplicate all child records of a duplicated record
*
* @param string $table
* @param integer $insertID
* @param integer $id
* @param integer $parentId
*/
protected function copyChilds($table, $insertID, $id, $parentId)
{
$time = time();
$copy = array();
$cctable = array();
$ctable = $GLOBALS['TL_DCA'][$table]['config']['ctable'];
if (!$GLOBALS['TL_DCA'][$table]['config']['ptable'] && strlen(Input::get('childs')) && $this->Database->fieldExists('pid', $table) && $this->Database->fieldExists('sorting', $table))
{
$ctable[] = $table;
}
if (!is_array($ctable))
{
return;
}
// Walk through each child table
foreach ($ctable as $v)
{
$this->loadDataContainer($v);
$cctable[$v] = $GLOBALS['TL_DCA'][$v]['config']['ctable'];
if (!$GLOBALS['TL_DCA'][$v]['config']['doNotCopyRecords'] && strlen($v))
{
// Consider the dynamic parent table (see #4867)
if ($GLOBALS['TL_DCA'][$v]['config']['dynamicPtable'])
{
$ptable = $GLOBALS['TL_DCA'][$v]['config']['ptable'];
$cond = ($ptable == 'tl_article') ? "(ptable=? OR ptable='')" : "ptable=?"; // backwards compatibility
$objCTable = $this->Database->prepare("SELECT * FROM $v WHERE pid=? AND $cond" . ($this->Database->fieldExists('sorting', $v) ? " ORDER BY sorting" : ""))
->execute($id, $ptable);
}
else
{
$objCTable = $this->Database->prepare("SELECT * FROM $v WHERE pid=?" . ($this->Database->fieldExists('sorting', $v) ? " ORDER BY sorting" : ""))
->execute($id);
}
while ($objCTable->next())
{
// Exclude the duplicated record itself
if ($v == $table && $objCTable->id == $parentId)
{
continue;
}
foreach ($objCTable->row() as $kk=>$vv)
{
if ($kk == 'id')
{
continue;
}
// Never copy passwords
if ($GLOBALS['TL_DCA'][$v]['fields'][$kk]['inputType'] == 'password')
{
$vv = Widget::getEmptyValueByFieldType($GLOBALS['TL_DCA'][$v]['fields'][$kk]['sql']);
}
// Empty unique fields or add a unique identifier in copyAll mode
elseif ($GLOBALS['TL_DCA'][$v]['fields'][$kk]['eval']['unique'])
{
$vv = (Input::get('act') == 'copyAll') ? $vv .'-'. substr(md5(uniqid(mt_rand(), true)), 0, 8) : Widget::getEmptyValueByFieldType($GLOBALS['TL_DCA'][$v]['fields'][$kk]['sql']);
}
// Reset doNotCopy and fallback fields to their default value
elseif ($GLOBALS['TL_DCA'][$v]['fields'][$kk]['eval']['doNotCopy'] || $GLOBALS['TL_DCA'][$v]['fields'][$kk]['eval']['fallback'])
{
$vv = '';
// Use array_key_exists to allow NULL (see #5252)
if (array_key_exists('default', $GLOBALS['TL_DCA'][$v]['fields'][$kk]))
{
$vv = is_array($GLOBALS['TL_DCA'][$v]['fields'][$kk]['default']) ? serialize($GLOBALS['TL_DCA'][$v]['fields'][$kk]['default']) : $GLOBALS['TL_DCA'][$v]['fields'][$kk]['default'];
}
// Encrypt the default value (see #3740)
if ($GLOBALS['TL_DCA'][$v]['fields'][$kk]['eval']['encrypt'])
{
$vv = Encryption::encrypt($vv);
}
}
$copy[$v][$objCTable->id][$kk] = $vv;
}
$copy[$v][$objCTable->id]['pid'] = $insertID;
$copy[$v][$objCTable->id]['tstamp'] = $time;
}
}
}
// Duplicate the child records
foreach ($copy as $k=>$v)
{
if (!empty($v))
{
foreach ($v as $kk=>$vv)
{
$objInsertStmt = $this->Database->prepare("INSERT INTO " . $k . " %s")
->set($vv)
->execute();
if ($objInsertStmt->affectedRows)
{
$insertID = $objInsertStmt->insertId;
if ((!empty($cctable[$k]) || $GLOBALS['TL_DCA'][$k]['list']['sorting']['mode'] == 5) && $kk != $parentId)
{
$this->copyChilds($k, $insertID, $kk, $parentId);
}
}
}
}
}
}
/**
* Move all selected records
*/
public function copyAll()
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['notCopyable'])
{
$this->log('Table "'.$this->strTable.'" is not copyable', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
$arrClipboard = $this->Session->get('CLIPBOARD');
if (isset($arrClipboard[$this->strTable]) && is_array($arrClipboard[$this->strTable]['id']))
{
foreach ($arrClipboard[$this->strTable]['id'] as $id)
{
$this->intId = $id;
$id = $this->copy(true);
Input::setGet('pid', $id);
Input::setGet('mode', 1);
}
}
$this->redirect($this->getReferer());
}
/**
* Calculate the new position of a moved or inserted record
*
* @param string $mode
* @param integer $pid
* @param boolean $insertInto
*/
protected function getNewPosition($mode, $pid=null, $insertInto=false)
{
// If there is pid and sorting
if ($this->Database->fieldExists('pid', $this->strTable) && $this->Database->fieldExists('sorting', $this->strTable))
{
// PID is not set - only valid for duplicated records, as they get the same parent ID as the original record!
if ($pid === null && $this->intId && $mode == 'copy')
{
$pid = $this->intId;
}
// PID is set (insert after or into the parent record)
if (is_numeric($pid))
{
$newPID = null;
$newSorting = null;
$session = $this->Session->getData();
$filter = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 4) ? $this->strTable.'_'.CURRENT_ID : $this->strTable;
// Consider the pagination menu when inserting at the top (see #7895)
if ($insertInto && isset($session['filter'][$filter]['limit']))
{
$limit = substr($session['filter'][$filter]['limit'], 0, strpos($session['filter'][$filter]['limit'], ','));
if ($limit > 0)
{
$objInsertAfter = $this->Database->prepare("SELECT id FROM " . $this->strTable . " WHERE pid=? ORDER BY sorting" )
->limit(1, $limit - 1)
->execute($pid);
if ($objInsertAfter->numRows)
{
$insertInto = false;
$pid = $objInsertAfter->id;
}
}
}
// Insert the current record at the beginning when inserting into the parent record
if ($insertInto)
{
$newPID = $pid;
$objSorting = $this->Database->prepare("SELECT MIN(sorting) AS sorting FROM " . $this->strTable . " WHERE pid=?")
->execute($pid);
// Select sorting value of the first record
if ($objSorting->numRows)
{
$curSorting = $objSorting->sorting;
// Resort if the new sorting value is not an integer or smaller than 1
if (($curSorting % 2) != 0 || $curSorting < 1)
{
$objNewSorting = $this->Database->prepare("SELECT id FROM " . $this->strTable . " WHERE pid=? ORDER BY sorting" )
->execute($pid);
$count = 2;
$newSorting = 128;
while ($objNewSorting->next())
{
$this->Database->prepare("UPDATE " . $this->strTable . " SET sorting=? WHERE id=?")
->limit(1)
->execute(($count++ * 128), $objNewSorting->id);
}
}
// Else new sorting = (current sorting / 2)
else $newSorting = ($curSorting / 2);
}
// Else new sorting = 128
else $newSorting = 128;
}
// Else insert the current record after the parent record
elseif ($pid > 0)
{
$objSorting = $this->Database->prepare("SELECT pid, sorting FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($pid);
// Set parent ID of the current record as new parent ID
if ($objSorting->numRows)
{
$newPID = $objSorting->pid;
$curSorting = $objSorting->sorting;
// Do not proceed without a parent ID
if (is_numeric($newPID))
{
$objNextSorting = $this->Database->prepare("SELECT MIN(sorting) AS sorting FROM " . $this->strTable . " WHERE pid=? AND sorting>?")
->execute($newPID, $curSorting);
// Select sorting value of the next record
if ($objNextSorting->sorting !== null)
{
$nxtSorting = $objNextSorting->sorting;
// Resort if the new sorting value is no integer or bigger than a MySQL integer
if ((($curSorting + $nxtSorting) % 2) != 0 || $nxtSorting >= 4294967295)
{
$count = 1;
$objNewSorting = $this->Database->prepare("SELECT id, sorting FROM " . $this->strTable . " WHERE pid=? ORDER BY sorting")
->execute($newPID);
while ($objNewSorting->next())
{
$this->Database->prepare("UPDATE " . $this->strTable . " SET sorting=? WHERE id=?")
->execute(($count++ * 128), $objNewSorting->id);
if ($objNewSorting->sorting == $curSorting)
{
$newSorting = ($count++ * 128);
}
}
}
// Else new sorting = (current sorting + next sorting) / 2
else $newSorting = (($curSorting + $nxtSorting) / 2);
}
// Else new sorting = (current sorting + 128)
else $newSorting = ($curSorting + 128);
}
}
// Use the given parent ID as parent ID
else
{
$newPID = $pid;
$newSorting = 128;
}
}
// Set new sorting and new parent ID
$this->set['pid'] = intval($newPID);
$this->set['sorting'] = intval($newSorting);
}
}
// If there is only pid
elseif ($this->Database->fieldExists('pid', $this->strTable))
{
// PID is not set - only valid for duplicated records, as they get the same parent ID as the original record!
if ($pid === null && $this->intId && $mode == 'copy')
{
$pid = $this->intId;
}
// PID is set (insert after or into the parent record)
if (is_numeric($pid))
{
// Insert the current record into the parent record
if ($insertInto)
{
$this->set['pid'] = $pid;
}
// Else insert the current record after the parent record
elseif ($pid > 0)
{
$objParentRecord = $this->Database->prepare("SELECT pid FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($pid);
if ($objParentRecord->numRows)
{
$this->set['pid'] = $objParentRecord->pid;
}
}
}
}
// If there is only sorting
elseif ($this->Database->fieldExists('sorting', $this->strTable))
{
// ID is set (insert after the current record)
if ($this->intId)
{
$objCurrentRecord = $this->Database->prepare("SELECT * FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($this->intId);
// Select current record
if ($objCurrentRecord->numRows)
{
$newSorting = null;
$curSorting = $objCurrentRecord->sorting;
$objNextSorting = $this->Database->prepare("SELECT MIN(sorting) AS sorting FROM " . $this->strTable . " WHERE sorting>?")
->execute($curSorting);
// Select sorting value of the next record
if ($objNextSorting->numRows)
{
$nxtSorting = $objNextSorting->sorting;
// Resort if the new sorting value is no integer or bigger than a MySQL integer field
if ((($curSorting + $nxtSorting) % 2) != 0 || $nxtSorting >= 4294967295)
{
$count = 1;
$objNewSorting = $this->Database->execute("SELECT id, sorting FROM " . $this->strTable . " ORDER BY sorting");
while ($objNewSorting->next())
{
$this->Database->prepare("UPDATE " . $this->strTable . " SET sorting=? WHERE id=?")
->execute(($count++ * 128), $objNewSorting->id);
if ($objNewSorting->sorting == $curSorting)
{
$newSorting = ($count++ * 128);
}
}
}
// Else new sorting = (current sorting + next sorting) / 2
else $newSorting = (($curSorting + $nxtSorting) / 2);
}
// Else new sorting = (current sorting + 128)
else $newSorting = ($curSorting + 128);
// Set new sorting
$this->set['sorting'] = intval($newSorting);
return;
}
}
// ID is not set or not found (insert at the end)
$objNextSorting = $this->Database->execute("SELECT MAX(sorting) AS sorting FROM " . $this->strTable);
$this->set['sorting'] = (intval($objNextSorting->sorting) + 128);
}
}
/**
* Delete a record of the current table table and save it to tl_undo
*
* @param boolean $blnDoNotRedirect
*/
public function delete($blnDoNotRedirect=false)
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['notDeletable'])
{
$this->log('Table "'.$this->strTable.'" is not deletable', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
if (!$this->intId)
{
$this->redirect($this->getReferer());
}
$delete = array();
// Do not save records from tl_undo itself
if ($this->strTable == 'tl_undo')
{
$this->Database->prepare("DELETE FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($this->intId);
$this->redirect($this->getReferer());
}
// If there is a PID field but no parent table
if ($this->Database->fieldExists('pid', $this->strTable) && !strlen($this->ptable))
{
$delete[$this->strTable] = $this->Database->getChildRecords($this->intId, $this->strTable);
array_unshift($delete[$this->strTable], $this->intId);
}
else
{
$delete[$this->strTable] = array($this->intId);
}
// Delete all child records if there is a child table
if (!empty($this->ctable))
{
foreach ($delete[$this->strTable] as $id)
{
$this->deleteChilds($this->strTable, $id, $delete);
}
}
$affected = 0;
$data = array();
// Save each record of each table
foreach ($delete as $table=>$fields)
{
foreach ($fields as $k=>$v)
{
$objSave = $this->Database->prepare("SELECT * FROM " . $table . " WHERE id=?")
->limit(1)
->execute($v);
if ($objSave->numRows)
{
$data[$table][$k] = $objSave->row();
// Store the active record
if ($table == $this->strTable && $v == $this->intId)
{
$this->objActiveRecord = $objSave;
}
}
$affected++;
}
}
$this->import('BackendUser', 'User');
$objUndoStmt = $this->Database->prepare("INSERT INTO tl_undo (pid, tstamp, fromTable, query, affectedRows, data) VALUES (?, ?, ?, ?, ?, ?)")
->execute($this->User->id, time(), $this->strTable, 'DELETE FROM '.$this->strTable.' WHERE id='.$this->intId, $affected, serialize($data));
// Delete the records
if ($objUndoStmt->affectedRows)
{
$insertID = $objUndoStmt->insertId;
// Call ondelete_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['config']['ondelete_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['config']['ondelete_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($this, $insertID);
}
elseif (is_callable($callback))
{
$callback($this, $insertID);
}
}
}
// Delete the records
foreach ($delete as $table=>$fields)
{
foreach ($fields as $v)
{
$this->Database->prepare("DELETE FROM " . $table . " WHERE id=?")
->limit(1)
->execute($v);
}
}
// Add a log entry unless we are deleting from tl_log itself
if ($this->strTable != 'tl_log')
{
$this->log('DELETE FROM '.$this->strTable.' WHERE id='.$data[$this->strTable][0]['id'], __METHOD__, TL_GENERAL);
}
}
if (!$blnDoNotRedirect)
{
$this->redirect($this->getReferer());
}
}
/**
* Delete all selected records
*/
public function deleteAll()
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['notDeletable'])
{
$this->log('Table "'.$this->strTable.'" is not deletable', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
$session = $this->Session->getData();
$ids = $session['CURRENT']['IDS'];
if (is_array($ids) && strlen($ids[0]))
{
foreach ($ids as $id)
{
$this->intId = $id;
$this->delete(true);
}
}
$this->redirect($this->getReferer());
}
/**
* Recursively get all related table names and records
*
* @param string $table
* @param integer $id
* @param array $delete
*/
public function deleteChilds($table, $id, &$delete)
{
$cctable = array();
$ctable = $GLOBALS['TL_DCA'][$table]['config']['ctable'];
if (!is_array($ctable))
{
return;
}
// Walk through each child table
foreach ($ctable as $v)
{
$this->loadDataContainer($v);
$cctable[$v] = $GLOBALS['TL_DCA'][$v]['config']['ctable'];
// Consider the dynamic parent table (see #4867)
if ($GLOBALS['TL_DCA'][$v]['config']['dynamicPtable'])
{
$ptable = $GLOBALS['TL_DCA'][$v]['config']['ptable'];
$cond = ($ptable == 'tl_article') ? "(ptable=? OR ptable='')" : "ptable=?"; // backwards compatibility
$objDelete = $this->Database->prepare("SELECT id FROM $v WHERE pid=? AND $cond")
->execute($id, $ptable);
}
else
{
$objDelete = $this->Database->prepare("SELECT id FROM $v WHERE pid=?")
->execute($id);
}
if (!$GLOBALS['TL_DCA'][$v]['config']['doNotDeleteRecords'] && strlen($v) && $objDelete->numRows)
{
foreach ($objDelete->fetchAllAssoc() as $row)
{
$delete[$v][] = $row['id'];
if (!empty($cctable[$v]))
{
$this->deleteChilds($v, $row['id'], $delete);
}
}
}
}
}
/**
* Restore one or more deleted records
*/
public function undo()
{
$objRecords = $this->Database->prepare("SELECT * FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($this->intId);
// Check whether there is a record
if ($objRecords->numRows < 1)
{
$this->redirect($this->getReferer());
}
$error = false;
$query = $objRecords->query;
$data = deserialize($objRecords->data);
if (!is_array($data))
{
$this->redirect($this->getReferer());
}
$arrFields = array();
// Restore the data
foreach ($data as $table=>$fields)
{
$this->loadDataContainer($table);
// Get the currently available fields
if (!isset($arrFields[$table]))
{
$arrFields[$table] = array_flip($this->Database->getFieldnames($table));
}
foreach ($fields as $row)
{
// Unset fields that no longer exist in the database
$row = array_intersect_key($row, $arrFields[$table]);
// Re-insert the data
$objInsertStmt = $this->Database->prepare("INSERT INTO " . $table . " %s")
->set($row)
->execute();
// Do not delete record from tl_undo if there is an error
if ($objInsertStmt->affectedRows < 1)
{
$error = true;
}
// Trigger the undo_callback
if (is_array($GLOBALS['TL_DCA'][$table]['config']['onundo_callback']))
{
foreach ($GLOBALS['TL_DCA'][$table]['config']['onundo_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($table, $row, $this);
}
elseif (is_callable($callback))
{
$callback($table, $row, $this);
}
}
}
}
}
// Add log entry and delete record from tl_undo if there was no error
if (!$error)
{
$this->log('Undone '. $query, __METHOD__, TL_GENERAL);
$this->Database->prepare("DELETE FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($this->intId);
}
$this->redirect($this->getReferer());
}
/**
* Change the order of two neighbour database records
*/
public function move()
{
// Proceed only if all mandatory variables are set
if ($this->intId && Input::get('sid') && (!$GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['root'] || !in_array($this->intId, $this->root)))
{
$objRow = $this->Database->prepare("SELECT * FROM " . $this->strTable . " WHERE id=? OR id=?")
->limit(2)
->execute($this->intId, Input::get('sid'));
$row = $objRow->fetchAllAssoc();
if ($row[0]['pid'] == $row[1]['pid'])
{
$this->Database->prepare("UPDATE " . $this->strTable . " SET sorting=? WHERE id=?")
->execute($row[0]['sorting'], $row[1]['id']);
$this->Database->prepare("UPDATE " . $this->strTable . " SET sorting=? WHERE id=?")
->execute($row[1]['sorting'], $row[0]['id']);
}
}
$this->redirect($this->getReferer());
}
/**
* Auto-generate a form to edit the current database record
*
* @param integer $intId
* @param integer $ajaxId
*
* @return string
*/
public function edit($intId=null, $ajaxId=null)
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['notEditable'])
{
$this->log('Table "'.$this->strTable.'" is not editable', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
if ($intId != '')
{
$this->intId = $intId;
}
// Get the current record
$objRow = $this->Database->prepare("SELECT * FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($this->intId);
// Redirect if there is no record with the given ID
if ($objRow->numRows < 1)
{
$this->log('Could not load record "'.$this->strTable.'.id='.$this->intId.'"', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
$this->objActiveRecord = $objRow;
$return = '';
$this->values[] = $this->intId;
$this->procedure[] = 'id=?';
$this->blnCreateNewVersion = false;
$objVersions = new Versions($this->strTable, $this->intId);
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['hideVersionMenu'])
{
// Compare versions
if (Input::get('versions'))
{
$objVersions->compare();
}
// Restore a version
if (Input::post('FORM_SUBMIT') == 'tl_version' && Input::post('version') != '')
{
$objVersions->restore(Input::post('version'));
$this->reload();
}
}
$objVersions->initialize();
// Build an array from boxes and rows
$this->strPalette = $this->getPalette();
$boxes = trimsplit(';', $this->strPalette);
$legends = array();
if (!empty($boxes))
{
foreach ($boxes as $k=>$v)
{
$eCount = 1;
$boxes[$k] = trimsplit(',', $v);
foreach ($boxes[$k] as $kk=>$vv)
{
if (preg_match('/^[.*]$/', $vv))
{
++$eCount;
continue;
}
if (preg_match('/^{.*}$/', $vv))
{
$legends[$k] = substr($vv, 1, -1);
unset($boxes[$k][$kk]);
}
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$vv]['exclude'] || !is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$vv]))
{
unset($boxes[$k][$kk]);
}
}
// Unset a box if it does not contain any fields
if (count($boxes[$k]) < $eCount)
{
unset($boxes[$k]);
}
}
$class = 'tl_tbox';
$fs = $this->Session->get('fieldset_states');
$blnIsFirst = true;
// Render boxes
foreach ($boxes as $k=>$v)
{
$strAjax = '';
$blnAjax = false;
$key = '';
$cls = '';
$legend = '';
if (isset($legends[$k]))
{
list($key, $cls) = explode(':', $legends[$k]);
$legend = "n" . '<legend onclick="AjaxRequest.toggleFieldset(this,'' . $key . '','' . $this->strTable . '')">' . (isset($GLOBALS['TL_LANG'][$this->strTable][$key]) ? $GLOBALS['TL_LANG'][$this->strTable][$key] : $key) . '</legend>';
}
if (isset($fs[$this->strTable][$key]))
{
$class .= ($fs[$this->strTable][$key] ? '' : ' collapsed');
}
else
{
$class .= (($cls && $legend) ? ' ' . $cls : '');
}
$return .= "nn" . '<fieldset' . ($key ? ' id="pal_'.$key.'"' : '') . ' class="' . $class . ($legend ? '' : ' nolegend') . '">' . $legend;
// Build rows of the current box
foreach ($v as $vv)
{
if ($vv == '[EOF]')
{
if ($blnAjax && Environment::get('isAjaxRequest'))
{
return $strAjax . '<input type="hidden" name="FORM_FIELDS[]" value="'.specialchars($this->strPalette).'">';
}
$blnAjax = false;
$return .= "n" . '</div>';
continue;
}
if (preg_match('/^[.*]$/', $vv))
{
$thisId = 'sub_' . substr($vv, 1, -1);
$blnAjax = ($ajaxId == $thisId && Environment::get('isAjaxRequest')) ? true : false;
$return .= "n" . '<div id="'.$thisId.'">';
continue;
}
$this->strField = $vv;
$this->strInputName = $vv;
$this->varValue = $objRow->$vv;
// Autofocus the first field
if ($blnIsFirst && $GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['inputType'] == 'text')
{
$GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['eval']['autofocus'] = 'autofocus';
$blnIsFirst = false;
}
// Convert CSV fields (see #2890)
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['eval']['multiple'] && isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['eval']['csv']))
{
$this->varValue = trimsplit($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['eval']['csv'], $this->varValue);
}
// Call load_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['load_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['load_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->varValue = $this->{$callback[0]}->{$callback[1]}($this->varValue, $this);
}
elseif (is_callable($callback))
{
$this->varValue = $callback($this->varValue, $this);
}
}
}
// Re-set the current value
$this->objActiveRecord->{$this->strField} = $this->varValue;
// Build the row and pass the current palette string (thanks to Tristan Lins)
$blnAjax ? $strAjax .= $this->row($this->strPalette) : $return .= $this->row($this->strPalette);
}
$class = 'tl_box';
$return .= "n" . '</fieldset>';
}
}
// Versions overview
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['enableVersioning'] && !$GLOBALS['TL_DCA'][$this->strTable]['config']['hideVersionMenu'])
{
$version = $objVersions->renderDropdown();
}
else
{
$version = '';
}
// Submit buttons
$arrButtons = array();
$arrButtons['save'] = '<input type="submit" name="save" id="save" class="tl_submit" accesskey="s" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['save']).'">';
if (!Input::get('nb'))
{
$arrButtons['saveNclose'] = '<input type="submit" name="saveNclose" id="saveNclose" class="tl_submit" accesskey="c" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['saveNclose']).'">';
}
if (!Input::get('popup') && !$GLOBALS['TL_DCA'][$this->strTable]['config']['closed'] && !$GLOBALS['TL_DCA'][$this->strTable]['config']['notCreatable'])
{
$arrButtons['saveNcreate'] = '<input type="submit" name="saveNcreate" id="saveNcreate" class="tl_submit" accesskey="n" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['saveNcreate']).'">';
}
if (Input::get('s2e'))
{
$arrButtons['saveNedit'] = '<input type="submit" name="saveNedit" id="saveNedit" class="tl_submit" accesskey="e" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['saveNedit']).'">';
}
elseif (!Input::get('popup') && ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 4 || strlen($this->ptable) || $GLOBALS['TL_DCA'][$this->strTable]['config']['switchToEdit']))
{
$arrButtons['saveNback'] = '<input type="submit" name="saveNback" id="saveNback" class="tl_submit" accesskey="g" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['saveNback']).'">';
}
// Call the buttons_callback (see #4691)
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['edit']['buttons_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['edit']['buttons_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$arrButtons = $this->{$callback[0]}->{$callback[1]}($arrButtons, $this);
}
elseif (is_callable($callback))
{
$arrButtons = $callback($arrButtons, $this);
}
}
}
// Add the buttons and end the form
$return .= '
</div>
<div class="tl_formbody_submit">
<div class="tl_submit_container">
' . implode(' ', $arrButtons) . '
</div>
</div>
</form>
<script>
window.addEvent('domready', function() {
Theme.focusInput("'.$this->strTable.'");
});
</script>';
// Begin the form (-> DO NOT CHANGE THIS ORDER -> this way the onsubmit attribute of the form can be changed by a field)
$return = $version . '
<div id="tl_buttons">' . (Input::get('nb') ? ' ' : '
<a href="'.$this->getReferer(true).'" class="header_back" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['backBTTitle']).'" accesskey="b" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG']['MSC']['backBT'].'</a>') . '
</div>
'.Message::generate().'
<form action="'.ampersand(Environment::get('request'), true).'" id="'.$this->strTable.'" class="tl_form" method="post" enctype="' . ($this->blnUploadable ? 'multipart/form-data' : 'application/x-www-form-urlencoded') . '"'.(!empty($this->onsubmit) ? ' onsubmit="'.implode(' ', $this->onsubmit).'"' : '').'>
<div class="tl_formbody_edit">
<input type="hidden" name="FORM_SUBMIT" value="'.specialchars($this->strTable).'">
<input type="hidden" name="REQUEST_TOKEN" value="'.REQUEST_TOKEN.'">
<input type="hidden" name="FORM_FIELDS[]" value="'.specialchars($this->strPalette).'">'.($this->noReload ? '
<p class="tl_error">'.$GLOBALS['TL_LANG']['ERR']['general'].'</p>' : '').$return;
// Reload the page to prevent _POST variables from being sent twice
if (Input::post('FORM_SUBMIT') == $this->strTable && !$this->noReload)
{
$arrValues = $this->values;
array_unshift($arrValues, time());
// Trigger the onsubmit_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['config']['onsubmit_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['config']['onsubmit_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($this);
}
elseif (is_callable($callback))
{
$callback($this);
}
}
}
// Save the current version
if ($this->blnCreateNewVersion)
{
$objVersions->create();
// Call the onversion_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['config']['onversion_callback']))
{
@trigger_error('Using the onversion_callback has been deprecated and will no longer work in Contao 5.0. Use the oncreate_version_callback instead.', E_USER_DEPRECATED);
foreach ($GLOBALS['TL_DCA'][$this->strTable]['config']['onversion_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($this->strTable, $this->intId, $this);
}
elseif (is_callable($callback))
{
$callback($this->strTable, $this->intId, $this);
}
}
}
}
// Set the current timestamp (-> DO NOT CHANGE THE ORDER version - timestamp)
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['dynamicPtable'])
{
$this->Database->prepare("UPDATE " . $this->strTable . " SET ptable=?, tstamp=? WHERE id=?")
->execute($this->ptable, time(), $this->intId);
}
else
{
$this->Database->prepare("UPDATE " . $this->strTable . " SET tstamp=? WHERE id=?")
->execute(time(), $this->intId);
}
// Redirect
if (isset($_POST['saveNclose']))
{
Message::reset();
System::setCookie('BE_PAGE_OFFSET', 0, 0);
$this->redirect($this->getReferer());
}
elseif (isset($_POST['saveNedit']))
{
Message::reset();
System::setCookie('BE_PAGE_OFFSET', 0, 0);
$strUrl = $this->addToUrl($GLOBALS['TL_DCA'][$this->strTable]['list']['operations']['edit']['href'], false);
$strUrl = preg_replace('/(&)?(s2e|act)=[^&]*/i', '', $strUrl);
$this->redirect($strUrl);
}
elseif (isset($_POST['saveNback']))
{
Message::reset();
System::setCookie('BE_PAGE_OFFSET', 0, 0);
if ($this->ptable == '')
{
$this->redirect(TL_SCRIPT . '?do=' . Input::get('do'));
}
// TODO: try to abstract this
elseif (($this->ptable == 'tl_theme' && $this->strTable == 'tl_style_sheet') || ($this->ptable == 'tl_page' && $this->strTable == 'tl_article'))
{
$this->redirect($this->getReferer(false, $this->strTable));
}
else
{
$this->redirect($this->getReferer(false, $this->ptable));
}
}
elseif (isset($_POST['saveNcreate']))
{
Message::reset();
System::setCookie('BE_PAGE_OFFSET', 0, 0);
$strUrl = TL_SCRIPT . '?do=' . Input::get('do');
if (isset($_GET['table']))
{
$strUrl .= '&table=' . Input::get('table');
}
// Tree view
if ($this->treeView)
{
$strUrl .= '&act=create&mode=1&pid=' . $this->intId;
}
// Parent view
elseif ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 4)
{
$strUrl .= $this->Database->fieldExists('sorting', $this->strTable) ? '&act=create&mode=1&pid=' . $this->intId . '&id=' . $this->activeRecord->pid : '&act=create&mode=2&pid=' . $this->activeRecord->pid;
}
// List view
else
{
$strUrl .= ($this->ptable != '') ? '&act=create&mode=2&pid=' . CURRENT_ID : '&act=create';
}
$this->redirect($strUrl . '&rt=' . REQUEST_TOKEN);
}
$this->reload();
}
// Set the focus if there is an error
if ($this->noReload)
{
$return .= '
<script>
window.addEvent('domready', function() {
Backend.vScrollTo(($('' . $this->strTable . '').getElement('label.error').getPosition().y - 20));
});
</script>';
}
return $return;
}
/**
* Auto-generate a form to edit all records that are currently shown
*
* @param integer $intId
* @param integer $ajaxId
*
* @return string
*/
public function editAll($intId=null, $ajaxId=null)
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['notEditable'])
{
$this->log('Table "'.$this->strTable.'" is not editable', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
$return = '';
$this->import('BackendUser', 'User');
// Get current IDs from session
$session = $this->Session->getData();
$ids = $session['CURRENT']['IDS'];
if ($intId != '' && Environment::get('isAjaxRequest'))
{
$ids = array($intId);
}
// Save field selection in session
if (Input::post('FORM_SUBMIT') == $this->strTable.'_all' && Input::get('fields'))
{
$session['CURRENT'][$this->strTable] = Input::post('all_fields');
$this->Session->setData($session);
}
// Add fields
$fields = $session['CURRENT'][$this->strTable];
if (!empty($fields) && is_array($fields) && Input::get('fields'))
{
$class = 'tl_tbox';
// Walk through each record
foreach ($ids as $id)
{
$this->intId = $id;
$this->procedure = array('id=?');
$this->values = array($this->intId);
$this->blnCreateNewVersion = false;
$this->strPalette = trimsplit('[;,]', $this->getPalette());
$objVersions = new Versions($this->strTable, $this->intId);
$objVersions->initialize();
// Add meta fields if the current user is an administrator
if ($this->User->isAdmin)
{
if ($this->Database->fieldExists('sorting', $this->strTable))
{
array_unshift($this->strPalette, 'sorting');
}
if ($this->Database->fieldExists('pid', $this->strTable))
{
array_unshift($this->strPalette, 'pid');
}
// Ensure a minimum configuration
foreach (array('pid', 'sorting') as $f)
{
if (!isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$f]['label']))
{
$GLOBALS['TL_DCA'][$this->strTable]['fields'][$f]['label'] = &$GLOBALS['TL_LANG']['MSC'][$f];
}
if (!isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$f]['inputType']))
{
$GLOBALS['TL_DCA'][$this->strTable]['fields'][$f]['inputType'] = 'text';
}
if (!isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$f]['eval']))
{
$GLOBALS['TL_DCA'][$this->strTable]['fields'][$f]['eval'] = array('rgxp' => 'natural');
}
}
}
// Begin current row
$strAjax = '';
$blnAjax = false;
$return .= '
<div class="'.$class.'">';
$class = 'tl_box';
$formFields = array();
// Get the field values
$objRow = $this->Database->prepare("SELECT * FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($this->intId);
// Store the active record
$this->objActiveRecord = $objRow;
$blnIsFirst = true;
foreach ($this->strPalette as $v)
{
// Check whether field is excluded
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$v]['exclude'])
{
continue;
}
if ($v == '[EOF]')
{
if ($blnAjax && Environment::get('isAjaxRequest'))
{
return $strAjax . '<input type="hidden" name="FORM_FIELDS_'.$id.'[]" value="'.specialchars(implode(',', $formFields)).'">';
}
$blnAjax = false;
$return .= "n " . '</div>';
continue;
}
if (preg_match('/^[.*]$/', $v))
{
$thisId = 'sub_' . substr($v, 1, -1) . '_' . $id;
$blnAjax = ($ajaxId == $thisId && Environment::get('isAjaxRequest')) ? true : false;
$return .= "n " . '<div id="'.$thisId.'">';
continue;
}
if (!in_array($v, $fields))
{
continue;
}
$this->strField = $v;
$this->strInputName = $v.'_'.$this->intId;
$formFields[] = $v.'_'.$this->intId;
// Set the default value and try to load the current value from DB (see #5252)
if (array_key_exists('default', $GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]))
{
$this->varValue = is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['default']) ? serialize($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['default']) : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['default'];
}
if ($objRow->$v !== false)
{
$this->varValue = $objRow->$v;
}
// Autofocus the first field
if ($blnIsFirst && $GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['inputType'] == 'text')
{
$GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['eval']['autofocus'] = 'autofocus';
$blnIsFirst = false;
}
// Convert CSV fields (see #2890)
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['eval']['multiple'] && isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['eval']['csv']))
{
$this->varValue = trimsplit($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['eval']['csv'], $this->varValue);
}
// Call load_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['load_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['load_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->varValue = $this->{$callback[0]}->{$callback[1]}($this->varValue, $this);
}
elseif (is_callable($callback))
{
$this->varValue = $callback($this->varValue, $this);
}
}
}
// Re-set the current value
$this->objActiveRecord->{$this->strField} = $this->varValue;
// Build the row and pass the current palette string (thanks to Tristan Lins)
$blnAjax ? $strAjax .= $this->row($this->strPalette) : $return .= $this->row($this->strPalette);
}
// Close box
$return .= '
<input type="hidden" name="FORM_FIELDS_'.$this->intId.'[]" value="'.specialchars(implode(',', $formFields)).'">
</div>';
// Save record
if (Input::post('FORM_SUBMIT') == $this->strTable && !$this->noReload)
{
// Call the onsubmit_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['config']['onsubmit_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['config']['onsubmit_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($this);
}
elseif (is_callable($callback))
{
$callback($this);
}
}
}
// Create a new version
if ($this->blnCreateNewVersion)
{
$objVersions->create();
// Call the onversion_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['config']['onversion_callback']))
{
@trigger_error('Using the onversion_callback has been deprecated and will no longer work in Contao 5.0. Use the oncreate_version_callback instead.', E_USER_DEPRECATED);
foreach ($GLOBALS['TL_DCA'][$this->strTable]['config']['onversion_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($this->strTable, $this->intId, $this);
}
elseif (is_callable($callback))
{
$callback($this->strTable, $this->intId, $this);
}
}
}
}
// Set the current timestamp (-> DO NOT CHANGE ORDER version - timestamp)
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['dynamicPtable'])
{
$this->Database->prepare("UPDATE " . $this->strTable . " SET ptable=?, tstamp=? WHERE id=?")
->execute($this->ptable, time(), $this->intId);
}
else
{
$this->Database->prepare("UPDATE " . $this->strTable . " SET tstamp=? WHERE id=?")
->execute(time(), $this->intId);
}
}
}
// Submit buttons
$arrButtons = array();
$arrButtons['save'] = '<input type="submit" name="save" id="save" class="tl_submit" accesskey="s" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['save']).'">';
$arrButtons['saveNclose'] = '<input type="submit" name="saveNclose" id="saveNclose" class="tl_submit" accesskey="c" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['saveNclose']).'">';
// Call the buttons_callback (see #4691)
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['edit']['buttons_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['edit']['buttons_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$arrButtons = $this->{$callback[0]}->{$callback[1]}($arrButtons, $this);
}
elseif (is_callable($callback))
{
$arrButtons = $callback($arrButtons, $this);
}
}
}
// Add the form
$return = '
<form action="'.ampersand(Environment::get('request'), true).'" id="'.$this->strTable.'" class="tl_form" method="post" enctype="' . ($this->blnUploadable ? 'multipart/form-data' : 'application/x-www-form-urlencoded') . '">
<div class="tl_formbody_edit">
<input type="hidden" name="FORM_SUBMIT" value="'.$this->strTable.'">
<input type="hidden" name="REQUEST_TOKEN" value="'.REQUEST_TOKEN.'">'.($this->noReload ? '
<p class="tl_error">'.$GLOBALS['TL_LANG']['ERR']['general'].'</p>' : '').$return.'
</div>
<div class="tl_formbody_submit">
<div class="tl_submit_container">
' . implode(' ', $arrButtons) . '
</div>
</div>
</form>
<script>
window.addEvent('domready', function() {
Theme.focusInput("'.$this->strTable.'");
});
</script>';
// Set the focus if there is an error
if ($this->noReload)
{
$return .= '
<script>
window.addEvent('domready', function() {
Backend.vScrollTo(($('' . $this->strTable . '').getElement('label.error').getPosition().y - 20));
});
</script>';
}
// Reload the page to prevent _POST variables from being sent twice
if (Input::post('FORM_SUBMIT') == $this->strTable && !$this->noReload)
{
if (Input::post('saveNclose'))
{
System::setCookie('BE_PAGE_OFFSET', 0, 0);
$this->redirect($this->getReferer());
}
$this->reload();
}
}
// Else show a form to select the fields
else
{
$options = '';
$fields = array();
// Add fields of the current table
$fields = array_merge($fields, array_keys($GLOBALS['TL_DCA'][$this->strTable]['fields']));
// Add meta fields if the current user is an administrator
if ($this->User->isAdmin)
{
if ($this->Database->fieldExists('sorting', $this->strTable) && !in_array('sorting', $fields))
{
array_unshift($fields, 'sorting');
}
if ($this->Database->fieldExists('pid', $this->strTable) && !in_array('pid', $fields))
{
array_unshift($fields, 'pid');
}
}
// Show all non-excluded fields
foreach ($fields as $field)
{
if ($field == 'pid' || $field == 'sorting' || (!$GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['exclude'] && !$GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['doNotShow'] && (strlen($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['inputType']) || is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['input_field_callback']))))
{
$options .= '
<input type="checkbox" name="all_fields[]" id="all_'.$field.'" class="tl_checkbox" value="'.specialchars($field).'"> <label for="all_'.$field.'" class="tl_checkbox_label">'.($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label'][0] ?: $GLOBALS['TL_LANG']['MSC'][$field][0]).'</label><br>';
}
}
$blnIsError = ($_POST && empty($_POST['all_fields']));
// Return the select menu
$return .= '
<form action="'.ampersand(Environment::get('request'), true).'&fields=1" id="'.$this->strTable.'_all" class="tl_form" method="post">
<div class="tl_formbody_edit">
<input type="hidden" name="FORM_SUBMIT" value="'.$this->strTable.'_all">
<input type="hidden" name="REQUEST_TOKEN" value="'.REQUEST_TOKEN.'">'.($blnIsError ? '
<p class="tl_error">'.$GLOBALS['TL_LANG']['ERR']['general'].'</p>' : '').'
<div class="tl_tbox">
<fieldset class="tl_checkbox_container">
<legend'.($blnIsError ? ' class="error"' : '').'>'.$GLOBALS['TL_LANG']['MSC']['all_fields'][0].'</legend>
<input type="checkbox" id="check_all" class="tl_checkbox" onclick="Backend.toggleCheckboxes(this)"> <label for="check_all" style="color:#a6a6a6"><em>'.$GLOBALS['TL_LANG']['MSC']['selectAll'].'</em></label><br>'.$options.'
</fieldset>'.($blnIsError ? '
<p class="tl_error">'.$GLOBALS['TL_LANG']['ERR']['all_fields'].'</p>' : ((Config::get('showHelp') && strlen($GLOBALS['TL_LANG']['MSC']['all_fields'][1])) ? '
<p class="tl_help tl_tip">'.$GLOBALS['TL_LANG']['MSC']['all_fields'][1].'</p>' : '')).'
</div>
</div>
<div class="tl_formbody_submit">
<div class="tl_submit_container">
<input type="submit" name="save" id="save" class="tl_submit" accesskey="s" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['continue']).'">
</div>
</div>
</form>';
}
// Return
return '
<div id="tl_buttons">
<a href="'.$this->getReferer(true).'" class="header_back" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['backBTTitle']).'" accesskey="b" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG']['MSC']['backBT'].'</a>
</div>'.$return;
}
/**
* Auto-generate a form to override all records that are currently shown
*
* @return string
*/
public function overrideAll()
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['notEditable'])
{
$this->log('Table "'.$this->strTable.'" is not editable', __METHOD__, TL_ERROR);
$this->redirect('contao/main.php?act=error');
}
$return = '';
$this->import('BackendUser', 'User');
// Get current IDs from session
$session = $this->Session->getData();
$ids = $session['CURRENT']['IDS'];
// Save field selection in session
if (Input::post('FORM_SUBMIT') == $this->strTable.'_all' && Input::get('fields'))
{
$session['CURRENT'][$this->strTable] = Input::post('all_fields');
$this->Session->setData($session);
}
// Add fields
$fields = $session['CURRENT'][$this->strTable];
if (!empty($fields) && is_array($fields) && Input::get('fields'))
{
$class = 'tl_tbox';
$formFields = array();
// Save record
if (Input::post('FORM_SUBMIT') == $this->strTable)
{
foreach ($ids as $id)
{
$this->intId = $id;
$this->procedure = array('id=?');
$this->values = array($this->intId);
$this->blnCreateNewVersion = false;
// Get the field values
$objRow = $this->Database->prepare("SELECT * FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($this->intId);
// Store the active record
$this->objActiveRecord = $objRow;
$objVersions = new Versions($this->strTable, $this->intId);
$objVersions->initialize();
// Store all fields
foreach ($fields as $v)
{
// Check whether field is excluded
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$v]['exclude'])
{
continue;
}
$this->strField = $v;
$this->strInputName = $v;
$this->varValue = '';
// Make sure the new value is applied
$GLOBALS['TL_DCA'][$this->strTable]['fields'][$v]['eval']['alwaysSave'] = true;
// Store value
$this->row();
}
// Post processing
if (!$this->noReload)
{
// Call the onsubmit_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['config']['onsubmit_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['config']['onsubmit_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($this);
}
elseif (is_callable($callback))
{
$callback($this);
}
}
}
// Create a new version
if ($this->blnCreateNewVersion)
{
$objVersions->create();
// Call the onversion_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['config']['onversion_callback']))
{
@trigger_error('Using the onversion_callback has been deprecated and will no longer work in Contao 5.0. Use the oncreate_version_callback instead.', E_USER_DEPRECATED);
foreach ($GLOBALS['TL_DCA'][$this->strTable]['config']['onversion_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($this->strTable, $this->intId, $this);
}
elseif (is_callable($callback))
{
$callback($this->strTable, $this->intId, $this);
}
}
}
}
// Set the current timestamp (-> DO NOT CHANGE ORDER version - timestamp)
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['dynamicPtable'])
{
$this->Database->prepare("UPDATE " . $this->strTable . " SET ptable=?, tstamp=? WHERE id=?")
->execute($this->ptable, time(), $this->intId);
}
else
{
$this->Database->prepare("UPDATE " . $this->strTable . " SET tstamp=? WHERE id=?")
->execute(time(), $this->intId);
}
}
}
}
$blnIsFirst = true;
// Begin current row
$return .= '
<div class="'.$class.'">';
foreach ($fields as $v)
{
// Check whether field is excluded
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$v]['exclude'])
{
continue;
}
$formFields[] = $v;
$this->intId = 0;
$this->procedure = array('id=?');
$this->values = array($this->intId);
$this->strField = $v;
$this->strInputName = $v;
$this->varValue = '';
// Autofocus the first field
if ($blnIsFirst && $GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['inputType'] == 'text')
{
$GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['eval']['autofocus'] = 'autofocus';
$blnIsFirst = false;
}
// Disable auto-submit
$GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['eval']['submitOnChange'] = false;
$return .= $this->row();
}
// Close box
$return .= '
<input type="hidden" name="FORM_FIELDS[]" value="'.specialchars(implode(',', $formFields)).'">
</div>';
// Submit buttons
$arrButtons = array();
$arrButtons['save'] = '<input type="submit" name="save" id="save" class="tl_submit" accesskey="s" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['save']).'">';
$arrButtons['saveNclose'] = '<input type="submit" name="saveNclose" id="saveNclose" class="tl_submit" accesskey="c" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['saveNclose']).'">';
// Call the buttons_callback (see #4691)
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['edit']['buttons_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['edit']['buttons_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$arrButtons = $this->{$callback[0]}->{$callback[1]}($arrButtons, $this);
}
elseif (is_callable($callback))
{
$arrButtons = $callback($arrButtons, $this);
}
}
}
// Add the form
$return = '
<form action="'.ampersand(Environment::get('request'), true).'" id="'.$this->strTable.'" class="tl_form" method="post" enctype="' . ($this->blnUploadable ? 'multipart/form-data' : 'application/x-www-form-urlencoded') . '">
<div class="tl_formbody_edit">
<input type="hidden" name="FORM_SUBMIT" value="'.$this->strTable.'">
<input type="hidden" name="REQUEST_TOKEN" value="'.REQUEST_TOKEN.'">'.($this->noReload ? '
<p class="tl_error">'.$GLOBALS['TL_LANG']['ERR']['general'].'</p>' : '').$return.'
</div>
<div class="tl_formbody_submit">
<div class="tl_submit_container">
' . implode(' ', $arrButtons) . '
</div>
</div>
</form>';
// Set the focus if there is an error
if ($this->noReload)
{
$return .= '
<script>
window.addEvent('domready', function() {
Backend.vScrollTo(($('' . $this->strTable . '').getElement('label.error').getPosition().y - 20));
});
</script>';
}
// Reload the page to prevent _POST variables from being sent twice
if (Input::post('FORM_SUBMIT') == $this->strTable && !$this->noReload)
{
if (Input::post('saveNclose'))
{
System::setCookie('BE_PAGE_OFFSET', 0, 0);
$this->redirect($this->getReferer());
}
$this->reload();
}
}
// Else show a form to select the fields
else
{
$options = '';
$fields = array();
// Add fields of the current table
$fields = array_merge($fields, array_keys($GLOBALS['TL_DCA'][$this->strTable]['fields']));
// Add meta fields if the current user is an administrator
if ($this->User->isAdmin)
{
if ($this->Database->fieldExists('sorting', $this->strTable) && !in_array('sorting', $fields))
{
array_unshift($fields, 'sorting');
}
if ($this->Database->fieldExists('pid', $this->strTable) && !in_array('pid', $fields))
{
array_unshift($fields, 'pid');
}
}
// Show all non-excluded fields
foreach ($fields as $field)
{
if ($field == 'pid' || $field == 'sorting' || (!$GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['exclude'] && !$GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['doNotShow'] && (strlen($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['inputType']) || is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['input_field_callback']))))
{
$options .= '
<input type="checkbox" name="all_fields[]" id="all_'.$field.'" class="tl_checkbox" value="'.specialchars($field).'"> <label for="all_'.$field.'" class="tl_checkbox_label">'.($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label'][0] ?: $GLOBALS['TL_LANG']['MSC'][$field][0]).'</label><br>';
}
}
$blnIsError = ($_POST && empty($_POST['all_fields']));
// Return the select menu
$return .= '
<form action="'.ampersand(Environment::get('request'), true).'&fields=1" id="'.$this->strTable.'_all" class="tl_form" method="post">
<div class="tl_formbody_edit">
<input type="hidden" name="FORM_SUBMIT" value="'.$this->strTable.'_all">
<input type="hidden" name="REQUEST_TOKEN" value="'.REQUEST_TOKEN.'">'.($blnIsError ? '
<p class="tl_error">'.$GLOBALS['TL_LANG']['ERR']['general'].'</p>' : '').'
<div class="tl_tbox">
<fieldset class="tl_checkbox_container">
<legend'.($blnIsError ? ' class="error"' : '').'>'.$GLOBALS['TL_LANG']['MSC']['all_fields'][0].'</legend>
<input type="checkbox" id="check_all" class="tl_checkbox" onclick="Backend.toggleCheckboxes(this)"> <label for="check_all" style="color:#a6a6a6"><em>'.$GLOBALS['TL_LANG']['MSC']['selectAll'].'</em></label><br>'.$options.'
</fieldset>'.($blnIsError ? '
<p class="tl_error">'.$GLOBALS['TL_LANG']['ERR']['all_fields'].'</p>' : ((Config::get('showHelp') && strlen($GLOBALS['TL_LANG']['MSC']['all_fields'][1])) ? '
<p class="tl_help tl_tip">'.$GLOBALS['TL_LANG']['MSC']['all_fields'][1].'</p>' : '')).'
</div>
</div>
<div class="tl_formbody_submit">
<div class="tl_submit_container">
<input type="submit" name="save" id="save" class="tl_submit" accesskey="s" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['continue']).'">
</div>
</div>
</form>';
}
// Return
return '
<div id="tl_buttons">
<a href="'.$this->getReferer(true).'" class="header_back" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['backBTTitle']).'" accesskey="b" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG']['MSC']['backBT'].'</a>
</div>'.$return;
}
/**
* Save the current value
*
* @param mixed $varValue
*
* @throws Exception
*/
protected function save($varValue)
{
if (Input::post('FORM_SUBMIT') != $this->strTable)
{
return;
}
$arrData = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField];
// Convert date formats into timestamps
if ($varValue != '' && in_array($arrData['eval']['rgxp'], array('date', 'time', 'datim')))
{
$objDate = new Date($varValue, Date::getFormatFromRgxp($arrData['eval']['rgxp']));
$varValue = $objDate->tstamp;
}
// Make sure unique fields are unique
if ($arrData['eval']['unique'] && $varValue != '' && !$this->Database->isUniqueValue($this->strTable, $this->strField, $varValue, $this->objActiveRecord->id))
{
throw new Exception(sprintf($GLOBALS['TL_LANG']['ERR']['unique'], $arrData['label'][0] ?: $this->strField));
}
// Handle multi-select fields in "override all" mode
if (Input::get('act') == 'overrideAll' && ($arrData['inputType'] == 'checkbox' || $arrData['inputType'] == 'checkboxWizard') && $arrData['eval']['multiple'])
{
if ($this->objActiveRecord !== null)
{
$new = deserialize($varValue, true);
$old = deserialize($this->objActiveRecord->{$this->strField}, true);
// Call load_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['load_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['load_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$old = $this->{$callback[0]}->{$callback[1]}($old, $this);
}
elseif (is_callable($callback))
{
$old = $callback($old, $this);
}
}
}
switch (Input::post($this->strInputName . '_update'))
{
case 'add':
$varValue = array_values(array_unique(array_merge($old, $new)));
break;
case 'remove':
$varValue = array_values(array_diff($old, $new));
break;
case 'replace':
$varValue = $new;
break;
}
if (!is_array($varValue) || empty($varValue))
{
$varValue = $arrData['eval']['nullIfEmpty'] ? null : '';
}
elseif (isset($arrData['eval']['csv']))
{
$varValue = implode($arrData['eval']['csv'], $varValue); // see #2890
}
else
{
$varValue = serialize($varValue);
}
}
}
// Convert arrays (see #2890)
if ($arrData['eval']['multiple'] && isset($arrData['eval']['csv']))
{
$varValue = implode($arrData['eval']['csv'], deserialize($varValue, true));
}
// Trigger the save_callback
if (is_array($arrData['save_callback']))
{
foreach ($arrData['save_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$varValue = $this->{$callback[0]}->{$callback[1]}($varValue, $this);
}
elseif (is_callable($callback))
{
$varValue = $callback($varValue, $this);
}
}
}
// Save the value if there was no error
if (($varValue != '' || !$arrData['eval']['doNotSaveEmpty']) && ($this->varValue !== $varValue || $arrData['eval']['alwaysSave']))
{
// If the field is a fallback field, empty all other columns (see #6498)
if ($arrData['eval']['fallback'] && $varValue != '')
{
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 4)
{
$this->Database->prepare("UPDATE " . $this->strTable . " SET " . $this->strField . "='' WHERE pid=?")
->execute($this->activeRecord->pid);
}
else
{
$this->Database->execute("UPDATE " . $this->strTable . " SET " . $this->strField . "=''");
}
}
// Set the correct empty value (see #6284, #6373)
if ($varValue === '')
{
$varValue = Widget::getEmptyValueByFieldType($GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['sql']);
}
$arrValues = $this->values;
array_unshift($arrValues, $varValue);
$objUpdateStmt = $this->Database->prepare("UPDATE " . $this->strTable . " SET " . $this->strField . "=? WHERE " . implode(' AND ', $this->procedure))
->execute($arrValues);
if ($objUpdateStmt->affectedRows)
{
$this->blnCreateNewVersion = true;
$this->varValue = deserialize($varValue);
if (is_object($this->objActiveRecord))
{
$this->objActiveRecord->{$this->strField} = $this->varValue;
}
}
}
}
/**
* Return the name of the current palette
*
* @return string
*/
public function getPalette()
{
$palette = 'default';
$strPalette = $GLOBALS['TL_DCA'][$this->strTable]['palettes'][$palette];
// Check whether there are selector fields
if (!empty($GLOBALS['TL_DCA'][$this->strTable]['palettes']['__selector__']))
{
$sValues = array();
$subpalettes = array();
$objFields = $this->Database->prepare("SELECT * FROM " . $this->strTable . " WHERE id=?")
->limit(1)
->execute($this->intId);
// Get selector values from DB
if ($objFields->numRows > 0)
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['palettes']['__selector__'] as $name)
{
$trigger = $objFields->$name;
// Overwrite the trigger
if (Input::post('FORM_SUBMIT') == $this->strTable)
{
$key = (Input::get('act') == 'editAll') ? $name.'_'.$this->intId : $name;
if (isset($_POST[$key]))
{
$trigger = Input::post($key);
}
}
if ($trigger != '')
{
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$name]['inputType'] == 'checkbox' && !$GLOBALS['TL_DCA'][$this->strTable]['fields'][$name]['eval']['multiple'])
{
$sValues[] = $name;
// Look for a subpalette
if (strlen($GLOBALS['TL_DCA'][$this->strTable]['subpalettes'][$name]))
{
$subpalettes[$name] = $GLOBALS['TL_DCA'][$this->strTable]['subpalettes'][$name];
}
}
else
{
$sValues[] = $trigger;
$key = $name .'_'. $trigger;
// Look for a subpalette
if (strlen($GLOBALS['TL_DCA'][$this->strTable]['subpalettes'][$key]))
{
$subpalettes[$name] = $GLOBALS['TL_DCA'][$this->strTable]['subpalettes'][$key];
}
}
}
}
}
// Build possible palette names from the selector values
if (!count($sValues))
{
$names = array('default');
}
elseif (count($sValues) > 1)
{
foreach ($sValues as $k=>$v)
{
// Unset selectors that just trigger subpalettes (see #3738)
if (isset($GLOBALS['TL_DCA'][$this->strTable]['subpalettes'][$v]))
{
unset($sValues[$k]);
}
}
$names = $this->combiner($sValues);
}
else
{
$names = array($sValues[0]);
}
// Get an existing palette
foreach ($names as $paletteName)
{
if (strlen($GLOBALS['TL_DCA'][$this->strTable]['palettes'][$paletteName]))
{
$strPalette = $GLOBALS['TL_DCA'][$this->strTable]['palettes'][$paletteName];
break;
}
}
// Include subpalettes
foreach ($subpalettes as $k=>$v)
{
$strPalette = preg_replace('/b'. preg_quote($k, '/').'b/i', $k.',['.$k.'],'.$v.',[EOF]', $strPalette);
}
}
return $strPalette;
}
/**
* Delete all incomplete and unrelated records
*/
protected function reviseTable()
{
$reload = false;
$ptable = $GLOBALS['TL_DCA'][$this->strTable]['config']['ptable'];
$ctable = $GLOBALS['TL_DCA'][$this->strTable]['config']['ctable'];
$new_records = $this->Session->get('new_records');
// HOOK: add custom logic
if (isset($GLOBALS['TL_HOOKS']['reviseTable']) && is_array($GLOBALS['TL_HOOKS']['reviseTable']))
{
foreach ($GLOBALS['TL_HOOKS']['reviseTable'] as $callback)
{
$status = null;
if (is_array($callback))
{
$this->import($callback[0]);
$status = $this->{$callback[0]}->{$callback[1]}($this->strTable, $new_records[$this->strTable], $ptable, $ctable);
}
elseif (is_callable($callback))
{
$status = $callback($this->strTable, $new_records[$this->strTable], $ptable, $ctable);
}
if ($status === true)
{
$reload = true;
}
}
}
// Delete all new but incomplete records (tstamp=0)
if (!empty($new_records[$this->strTable]) && is_array($new_records[$this->strTable]))
{
$objStmt = $this->Database->execute("DELETE FROM " . $this->strTable . " WHERE id IN(" . implode(',', array_map('intval', $new_records[$this->strTable])) . ") AND tstamp=0");
if ($objStmt->affectedRows > 0)
{
$reload = true;
}
}
// Delete all records of the current table that are not related to the parent table
if ($ptable != '')
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['dynamicPtable'])
{
$objStmt = $this->Database->execute("DELETE FROM " . $this->strTable . " WHERE ptable='" . $ptable . "' AND NOT EXISTS (SELECT * FROM " . $ptable . " WHERE " . $this->strTable . ".pid = " . $ptable . ".id)");
}
else
{
$objStmt = $this->Database->execute("DELETE FROM " . $this->strTable . " WHERE NOT EXISTS (SELECT * FROM " . $ptable . " WHERE " . $this->strTable . ".pid = " . $ptable . ".id)");
}
if ($objStmt->affectedRows > 0)
{
$reload = true;
}
}
// Delete all records of the child table that are not related to the current table
if (!empty($ctable) && is_array($ctable))
{
foreach ($ctable as $v)
{
if ($v != '')
{
// Load the DCA configuration so we can check for "dynamicPtable"
if (!isset($GLOBALS['loadDataContainer'][$v]))
{
$this->loadDataContainer($v);
}
if ($GLOBALS['TL_DCA'][$v]['config']['dynamicPtable'])
{
$objStmt = $this->Database->execute("DELETE FROM $v WHERE ptable='" . $this->strTable . "' AND NOT EXISTS (SELECT * FROM " . $this->strTable . " WHERE $v.pid = " . $this->strTable . ".id)");
}
else
{
$objStmt = $this->Database->execute("DELETE FROM $v WHERE NOT EXISTS (SELECT * FROM " . $this->strTable . " WHERE $v.pid = " . $this->strTable . ".id)");
}
if ($objStmt->affectedRows > 0)
{
$reload = true;
}
}
}
}
// Reload the page
if ($reload)
{
$this->reload();
}
}
/**
* List all records of the current table as tree and return them as HTML string
*
* @return string
*/
protected function treeView()
{
$table = $this->strTable;
$treeClass = 'tl_tree';
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 6)
{
$table = $this->ptable;
$treeClass = 'tl_tree_xtnd';
System::loadLanguageFile($table);
$this->loadDataContainer($table);
}
$session = $this->Session->getData();
// Toggle the nodes
if (Input::get('ptg') == 'all')
{
$node = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 6) ? $this->strTable.'_'.$table.'_tree' : $this->strTable.'_tree';
// Expand tree
if (!is_array($session[$node]) || empty($session[$node]) || current($session[$node]) != 1)
{
$session[$node] = array();
$objNodes = $this->Database->execute("SELECT DISTINCT pid FROM " . $table . " WHERE pid>0");
while ($objNodes->next())
{
$session[$node][$objNodes->pid] = 1;
}
}
// Collapse tree
else
{
$session[$node] = array();
}
$this->Session->setData($session);
$this->redirect(preg_replace('/(&(amp;)?|?)ptg=[^& ]*/i', '', Environment::get('request')));
}
// Return if a mandatory field (id, pid, sorting) is missing
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 5 && (!$this->Database->fieldExists('id', $table) || !$this->Database->fieldExists('pid', $table) || !$this->Database->fieldExists('sorting', $table)))
{
return '
<p class="tl_empty">Table "'.$table.'" can not be shown as tree, because the "id", "pid" or "sorting" field is missing!</p>';
}
// Return if there is no parent table
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 6 && !strlen($this->ptable))
{
return '
<p class="tl_empty">Table "'.$table.'" can not be shown as extended tree, because there is no parent table!</p>';
}
$blnClipboard = false;
$arrClipboard = $this->Session->get('CLIPBOARD');
// Check the clipboard
if (!empty($arrClipboard[$this->strTable]))
{
$blnClipboard = true;
$arrClipboard = $arrClipboard[$this->strTable];
}
// Load the fonts to display the paste hint
Config::set('loadGoogleFonts', $blnClipboard);
$label = $GLOBALS['TL_DCA'][$table]['config']['label'];
$icon = $GLOBALS['TL_DCA'][$table]['list']['sorting']['icon'] ?: 'pagemounts.gif';
$label = Image::getHtml($icon).' <label>'.$label.'</label>';
// Begin buttons container
$return = '
<div id="tl_buttons">'.((Input::get('act') == 'select') ? '
<a href="'.$this->getReferer(true).'" class="header_back" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['backBTTitle']).'" accesskey="b" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG']['MSC']['backBT'].'</a> ' : (isset($GLOBALS['TL_DCA'][$this->strTable]['config']['backlink']) ? '
<a href="contao/main.php?'.$GLOBALS['TL_DCA'][$this->strTable]['config']['backlink'].'" class="header_back" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['backBTTitle']).'" accesskey="b" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG']['MSC']['backBT'].'</a> ' : '')) . ((Input::get('act') != 'select' && !$blnClipboard && !$GLOBALS['TL_DCA'][$this->strTable]['config']['closed'] && !$GLOBALS['TL_DCA'][$this->strTable]['config']['notCreatable']) ? '
<a href="'.$this->addToUrl('act=paste&mode=create').'" class="header_new" title="'.specialchars($GLOBALS['TL_LANG'][$this->strTable]['new'][1]).'" accesskey="n" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG'][$this->strTable]['new'][0].'</a> ' : '') . ($blnClipboard ? '
<a href="'.$this->addToUrl('clipboard=1').'" class="header_clipboard" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['clearClipboard']).'" accesskey="x">'.$GLOBALS['TL_LANG']['MSC']['clearClipboard'].'</a> ' : $this->generateGlobalButtons()) . '
</div>' . Message::generate(true);
$tree = '';
$blnHasSorting = $this->Database->fieldExists('sorting', $table);
$blnNoRecursion = false;
// Limit the results by modifying $this->root
if ($session['search'][$this->strTable]['value'] != '')
{
$for = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 6) ? 'pid' : 'id';
if ($session['search'][$this->strTable]['field'] == 'id')
{
$objRoot = $this->Database->prepare("SELECT $for FROM {$this->strTable} WHERE id=?")
->execute($session['search'][$this->strTable]['value']);
}
else
{
$strPattern = "CAST(%s AS CHAR) REGEXP ?";
if (substr(Config::get('dbCollation'), -3) == '_ci')
{
$strPattern = "LOWER(CAST(%s AS CHAR)) REGEXP LOWER(?)";
}
$fld = $session['search'][$this->strTable]['field'];
if (isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$fld]['foreignKey']))
{
list($t, $f) = explode('.', $GLOBALS['TL_DCA'][$this->strTable]['fields'][$fld]['foreignKey']);
$objRoot = $this->Database->prepare("SELECT $for FROM {$this->strTable} WHERE (" . sprintf($strPattern, $fld) . " OR " . sprintf($strPattern, "(SELECT $f FROM $t WHERE $t.id={$this->strTable}.$fld)") . ") GROUP BY $for")
->execute($session['search'][$this->strTable]['value'], $session['search'][$this->strTable]['value']);
}
else
{
$objRoot = $this->Database->prepare("SELECT $for FROM {$this->strTable} WHERE " . sprintf($strPattern, $fld) . " GROUP BY $for")
->execute($session['search'][$this->strTable]['value']);
}
}
if ($objRoot->numRows < 1)
{
$this->root = array();
}
else
{
// Respect existing limitations (root IDs)
if (is_array($GLOBALS['TL_DCA'][$table]['list']['sorting']['root']))
{
$arrRoot = array();
while ($objRoot->next())
{
if (count(array_intersect($this->root, $this->Database->getParentRecords($objRoot->$for, $table))) > 0)
{
$arrRoot[] = $objRoot->$for;
}
}
$this->root = $arrRoot;
}
else
{
$blnNoRecursion = true;
$this->root = $objRoot->fetchEach($for);
}
}
}
// Call a recursive function that builds the tree
for ($i=0, $c=count($this->root); $i<$c; $i++)
{
$tree .= $this->generateTree($table, $this->root[$i], array('p'=>$this->root[($i-1)], 'n'=>$this->root[($i+1)]), $blnHasSorting, -20, ($blnClipboard ? $arrClipboard : false), ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 5 && $blnClipboard && $this->root[$i] == $arrClipboard['id']), false, $blnNoRecursion);
}
// Return if there are no records
if ($tree == '' && Input::get('act') != 'paste')
{
return $return . '
<p class="tl_empty">'.$GLOBALS['TL_LANG']['MSC']['noResult'].'</p>';
}
$return .= ((Input::get('act') == 'select') ? '
<form action="'.ampersand(Environment::get('request'), true).'" id="tl_select" class="tl_form'.((Input::get('act') == 'select') ? ' unselectable' : '').'" method="post" novalidate>
<div class="tl_formbody">
<input type="hidden" name="FORM_SUBMIT" value="tl_select">
<input type="hidden" name="REQUEST_TOKEN" value="'.REQUEST_TOKEN.'">' : '').($blnClipboard ? '
<div id="paste_hint">
<p>'.$GLOBALS['TL_LANG']['MSC']['selectNewPosition'].'</p>
</div>' : '').'
<div class="tl_listing_container tree_view" id="tl_listing">'.(isset($GLOBALS['TL_DCA'][$table]['list']['sorting']['breadcrumb']) ? $GLOBALS['TL_DCA'][$table]['list']['sorting']['breadcrumb'] : '').((Input::get('act') == 'select') ? '
<div class="tl_select_trigger">
<label for="tl_select_trigger" class="tl_select_label">'.$GLOBALS['TL_LANG']['MSC']['selectAll'].'</label> <input type="checkbox" id="tl_select_trigger" onclick="Backend.toggleCheckboxes(this)" class="tl_tree_checkbox">
</div>' : '').'
<ul class="tl_listing '. $treeClass .'">
<li class="tl_folder_top"><div class="tl_left">'.$label.'</div> <div class="tl_right">';
$_buttons = ' ';
// Show paste button only if there are no root records specified
if (Input::get('act') != 'select' && $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 5 && $blnClipboard && ((!count($GLOBALS['TL_DCA'][$table]['list']['sorting']['root']) && $GLOBALS['TL_DCA'][$table]['list']['sorting']['root'] !== false) || $GLOBALS['TL_DCA'][$table]['list']['sorting']['rootPaste']))
{
// Call paste_button_callback (&$dc, $row, $table, $cr, $childs, $previous, $next)
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['paste_button_callback']))
{
$strClass = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['paste_button_callback'][0];
$strMethod = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['paste_button_callback'][1];
$this->import($strClass);
$_buttons = $this->$strClass->$strMethod($this, array('id'=>0), $table, false, $arrClipboard);
}
elseif (is_callable($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['paste_button_callback']))
{
$_buttons = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['paste_button_callback']($this, array('id'=>0), $table, false, $arrClipboard);
}
else
{
$imagePasteInto = Image::getHtml('pasteinto.gif', $GLOBALS['TL_LANG'][$this->strTable]['pasteinto'][0]);
$_buttons = '<a href="'.$this->addToUrl('act='.$arrClipboard['mode'].'&mode=2&pid=0'.(!is_array($arrClipboard['id']) ? '&id='.$arrClipboard['id'] : '')).'" title="'.specialchars($GLOBALS['TL_LANG'][$this->strTable]['pasteinto'][0]).'" onclick="Backend.getScrollOffset()">'.$imagePasteInto.'</a> ';
}
}
// End table
$return .= $_buttons . '</div><div style="clear:both"></div></li>'.$tree.'
</ul>
</div>';
// Close the form
if (Input::get('act') == 'select')
{
// Submit buttons
$arrButtons = array();
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notDeletable'])
{
$arrButtons['delete'] = '<input type="submit" name="delete" id="delete" class="tl_submit" accesskey="d" onclick="return confirm(''.$GLOBALS['TL_LANG']['MSC']['delAllConfirm'].'')" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['deleteSelected']).'">';
}
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notSortable'])
{
$arrButtons['cut'] = '<input type="submit" name="cut" id="cut" class="tl_submit" accesskey="x" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['moveSelected']).'">';
}
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notCopyable'])
{
$arrButtons['copy'] = '<input type="submit" name="copy" id="copy" class="tl_submit" accesskey="c" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['copySelected']).'">';
}
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notEditable'])
{
$arrButtons['override'] = '<input type="submit" name="override" id="override" class="tl_submit" accesskey="v" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['overrideSelected']).'">';
$arrButtons['edit'] = '<input type="submit" name="edit" id="edit" class="tl_submit" accesskey="s" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['editSelected']).'">';
}
// Call the buttons_callback (see #4691)
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['select']['buttons_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['select']['buttons_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$arrButtons = $this->{$callback[0]}->{$callback[1]}($arrButtons, $this);
}
elseif (is_callable($callback))
{
$arrButtons = $callback($arrButtons, $this);
}
}
}
$return .= '
<div class="tl_formbody_submit" style="text-align:right">
<div class="tl_submit_container">
' . implode(' ', $arrButtons) . '
</div>
</div>
</div>
</form>';
}
return $return;
}
/**
* Generate a particular subpart of the tree and return it as HTML string
*
* @param integer $id
* @param integer $level
*
* @return string
*/
public function ajaxTreeView($id, $level)
{
if (!Environment::get('isAjaxRequest'))
{
return '';
}
$return = '';
$table = $this->strTable;
$blnPtable = false;
// Load parent table
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 6)
{
$table = $this->ptable;
System::loadLanguageFile($table);
$this->loadDataContainer($table);
$blnPtable = true;
}
$blnProtected = false;
// Check protected pages
if ($table == 'tl_page')
{
$objParent = PageModel::findWithDetails($id);
$blnProtected = $objParent->protected ? true : false;
}
$margin = ($level * 20);
$hasSorting = $this->Database->fieldExists('sorting', $table);
$arrIds = array();
// Get records
$objRows = $this->Database->prepare("SELECT id FROM " . $table . " WHERE pid=?" . ($hasSorting ? " ORDER BY sorting" : ""))
->execute($id);
while ($objRows->next())
{
$arrIds[] = $objRows->id;
}
$blnClipboard = false;
$arrClipboard = $this->Session->get('CLIPBOARD');
// Check clipboard
if (!empty($arrClipboard[$this->strTable]))
{
$blnClipboard = true;
$arrClipboard = $arrClipboard[$this->strTable];
}
for ($i=0, $c=count($arrIds); $i<$c; $i++)
{
$return .= ' ' . trim($this->generateTree($table, $arrIds[$i], array('p'=>$arrIds[($i-1)], 'n'=>$arrIds[($i+1)]), $hasSorting, $margin, ($blnClipboard ? $arrClipboard : false), ($id == $arrClipboard ['id'] || (is_array($arrClipboard ['id']) && in_array($id, $arrClipboard ['id'])) || (!$blnPtable && !is_array($arrClipboard['id']) && in_array($id, $this->Database->getChildRecords($arrClipboard['id'], $table)))), $blnProtected));
}
return $return;
}
/**
* Recursively generate the tree and return it as HTML string
*
* @param string $table
* @param integer $id
* @param array $arrPrevNext
* @param boolean $blnHasSorting
* @param integer $intMargin
* @param array $arrClipboard
* @param boolean $blnCircularReference
* @param boolean $protectedPage
* @param boolean $blnNoRecursion
*
* @return string
*/
protected function generateTree($table, $id, $arrPrevNext, $blnHasSorting, $intMargin=0, $arrClipboard=null, $blnCircularReference=false, $protectedPage=false, $blnNoRecursion=false)
{
static $session;
$session = $this->Session->getData();
$node = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 6) ? $this->strTable.'_'.$table.'_tree' : $this->strTable.'_tree';
// Toggle nodes
if (Input::get('ptg'))
{
$session[$node][Input::get('ptg')] = (isset($session[$node][Input::get('ptg')]) && $session[$node][Input::get('ptg')] == 1) ? 0 : 1;
$this->Session->setData($session);
$this->redirect(preg_replace('/(&(amp;)?|?)ptg=[^& ]*/i', '', Environment::get('request')));
}
$objRow = $this->Database->prepare("SELECT * FROM " . $table . " WHERE id=?")
->limit(1)
->execute($id);
// Return if there is no result
if ($objRow->numRows < 1)
{
$this->Session->setData($session);
return '';
}
$return = '';
$intSpacing = 20;
$childs = array();
// Add the ID to the list of current IDs
if ($this->strTable == $table)
{
$this->current[] = $objRow->id;
}
// Check whether there are child records
if (!$blnNoRecursion)
{
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 5 || $this->strTable != $table)
{
$objChilds = $this->Database->prepare("SELECT id FROM " . $table . " WHERE pid=?" . ($blnHasSorting ? " ORDER BY sorting" : ''))
->execute($id);
if ($objChilds->numRows)
{
$childs = $objChilds->fetchEach('id');
}
}
}
$blnProtected = false;
// Check whether the page is protected
if ($table == 'tl_page')
{
$blnProtected = ($objRow->protected || $protectedPage) ? true : false;
}
$session[$node][$id] = (is_int($session[$node][$id])) ? $session[$node][$id] : 0;
$mouseover = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 5 || $table == $this->strTable) ? ' toggle_select" onmouseover="Theme.hoverDiv(this,1)" onmouseout="Theme.hoverDiv(this,0)"' : '"';
$return .= "n " . '<li class="'.((($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 5 && $objRow->type == 'root') || $table != $this->strTable) ? 'tl_folder' : 'tl_file').' click2edit'.$mouseover.'><div class="tl_left" style="padding-left:'.($intMargin + $intSpacing).'px">';
// Calculate label and add a toggle button
$args = array();
$folderAttribute = 'style="margin-left:20px"';
$showFields = $GLOBALS['TL_DCA'][$table]['list']['label']['fields'];
$level = ($intMargin / $intSpacing + 1);
if (!empty($childs))
{
$folderAttribute = '';
$img = ($session[$node][$id] == 1) ? 'folMinus.gif' : 'folPlus.gif';
$alt = ($session[$node][$id] == 1) ? $GLOBALS['TL_LANG']['MSC']['collapseNode'] : $GLOBALS['TL_LANG']['MSC']['expandNode'];
$return .= '<a href="'.$this->addToUrl('ptg='.$id).'" title="'.specialchars($alt).'" onclick="Backend.getScrollOffset();return AjaxRequest.toggleStructure(this,''.$node.'_'.$id.'','.$level.','.$GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'].')">'.Image::getHtml($img, '', 'style="margin-right:2px"').'</a>';
}
foreach ($showFields as $k=>$v)
{
// Decrypt the value
if ($GLOBALS['TL_DCA'][$table]['fields'][$v]['eval']['encrypt'])
{
$objRow->$v = Encryption::decrypt(deserialize($objRow->$v));
}
if (strpos($v, ':') !== false)
{
list($strKey, $strTable) = explode(':', $v);
list($strTable, $strField) = explode('.', $strTable);
$objRef = $this->Database->prepare("SELECT " . $strField . " FROM " . $strTable . " WHERE id=?")
->limit(1)
->execute($objRow->$strKey);
$args[$k] = $objRef->numRows ? $objRef->$strField : '';
}
elseif (in_array($GLOBALS['TL_DCA'][$table]['fields'][$v]['flag'], array(5, 6, 7, 8, 9, 10)))
{
$args[$k] = Date::parse(Config::get('datimFormat'), $objRow->$v);
}
elseif ($GLOBALS['TL_DCA'][$table]['fields'][$v]['inputType'] == 'checkbox' && !$GLOBALS['TL_DCA'][$table]['fields'][$v]['eval']['multiple'])
{
$args[$k] = ($objRow->$v != '') ? (isset($GLOBALS['TL_DCA'][$table]['fields'][$v]['label'][0]) ? $GLOBALS['TL_DCA'][$table]['fields'][$v]['label'][0] : $v) : '';
}
else
{
$args[$k] = $GLOBALS['TL_DCA'][$table]['fields'][$v]['reference'][$objRow->$v] ?: $objRow->$v;
}
}
$label = vsprintf(((strlen($GLOBALS['TL_DCA'][$table]['list']['label']['format'])) ? $GLOBALS['TL_DCA'][$table]['list']['label']['format'] : '%s'), $args);
// Shorten the label if it is too long
if ($GLOBALS['TL_DCA'][$table]['list']['label']['maxCharacters'] > 0 && $GLOBALS['TL_DCA'][$table]['list']['label']['maxCharacters'] < utf8_strlen(strip_tags($label)))
{
$label = trim(StringUtil::substrHtml($label, $GLOBALS['TL_DCA'][$table]['list']['label']['maxCharacters'])) . ' …';
}
$label = preg_replace('/() ?|[] ?|{} ?|<> ?/', '', $label);
// Call the label_callback ($row, $label, $this)
if (is_array($GLOBALS['TL_DCA'][$table]['list']['label']['label_callback']))
{
$strClass = $GLOBALS['TL_DCA'][$table]['list']['label']['label_callback'][0];
$strMethod = $GLOBALS['TL_DCA'][$table]['list']['label']['label_callback'][1];
$this->import($strClass);
$return .= $this->$strClass->$strMethod($objRow->row(), $label, $this, $folderAttribute, false, $blnProtected);
}
elseif (is_callable($GLOBALS['TL_DCA'][$table]['list']['label']['label_callback']))
{
$return .= $GLOBALS['TL_DCA'][$table]['list']['label']['label_callback']($objRow->row(), $label, $this, $folderAttribute, false, $blnProtected);
}
else
{
$return .= Image::getHtml('iconPLAIN.gif', '') . ' ' . $label;
}
$return .= '</div> <div class="tl_right">';
$previous = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 6) ? $arrPrevNext['pp'] : $arrPrevNext['p'];
$next = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 6) ? $arrPrevNext['nn'] : $arrPrevNext['n'];
$_buttons = '';
// Regular buttons ($row, $table, $root, $blnCircularReference, $childs, $previous, $next)
if ($this->strTable == $table)
{
$_buttons .= (Input::get('act') == 'select') ? '<input type="checkbox" name="IDS[]" id="ids_'.$id.'" class="tl_tree_checkbox" value="'.$id.'">' : $this->generateButtons($objRow->row(), $table, $this->root, $blnCircularReference, $childs, $previous, $next);
}
// Paste buttons
if ($arrClipboard !== false && Input::get('act') != 'select')
{
$_buttons .= ' ';
// Call paste_button_callback(&$dc, $row, $table, $blnCircularReference, $arrClipboard, $childs, $previous, $next)
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['paste_button_callback']))
{
$strClass = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['paste_button_callback'][0];
$strMethod = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['paste_button_callback'][1];
$this->import($strClass);
$_buttons .= $this->$strClass->$strMethod($this, $objRow->row(), $table, $blnCircularReference, $arrClipboard, $childs, $previous, $next);
}
elseif (is_callable($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['paste_button_callback']))
{
$_buttons .= $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['paste_button_callback']($this, $objRow->row(), $table, $blnCircularReference, $arrClipboard, $childs, $previous, $next);
}
else
{
$imagePasteAfter = Image::getHtml('pasteafter.gif', sprintf($GLOBALS['TL_LANG'][$this->strTable]['pasteafter'][1], $id));
$imagePasteInto = Image::getHtml('pasteinto.gif', sprintf($GLOBALS['TL_LANG'][$this->strTable]['pasteinto'][1], $id));
// Regular tree (on cut: disable buttons of the page all its childs to avoid circular references)
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 5)
{
$_buttons .= ($arrClipboard['mode'] == 'cut' && ($blnCircularReference || $arrClipboard['id'] == $id) || $arrClipboard['mode'] == 'cutAll' && ($blnCircularReference || in_array($id, $arrClipboard['id'])) || (!empty($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['root']) && !$GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['rootPaste'] && in_array($id, $this->root))) ? Image::getHtml('pasteafter_.gif').' ' : '<a href="'.$this->addToUrl('act='.$arrClipboard['mode'].'&mode=1&pid='.$id.(!is_array($arrClipboard['id']) ? '&id='.$arrClipboard['id'] : '')).'" title="'.specialchars(sprintf($GLOBALS['TL_LANG'][$this->strTable]['pasteafter'][1], $id)).'" onclick="Backend.getScrollOffset()">'.$imagePasteAfter.'</a> ';
$_buttons .= ($arrClipboard['mode'] == 'paste' && ($blnCircularReference || $arrClipboard['id'] == $id) || $arrClipboard['mode'] == 'cutAll' && ($blnCircularReference || in_array($id, $arrClipboard['id']))) ? Image::getHtml('pasteinto_.gif').' ' : '<a href="'.$this->addToUrl('act='.$arrClipboard['mode'].'&mode=2&pid='.$id.(!is_array($arrClipboard['id']) ? '&id='.$arrClipboard['id'] : '')).'" title="'.specialchars(sprintf($GLOBALS['TL_LANG'][$this->strTable]['pasteinto'][1], $id)).'" onclick="Backend.getScrollOffset()">'.$imagePasteInto.'</a> ';
}
// Extended tree
else
{
$_buttons .= ($this->strTable == $table) ? (($arrClipboard['mode'] == 'cut' && ($blnCircularReference || $arrClipboard['id'] == $id) || $arrClipboard['mode'] == 'cutAll' && ($blnCircularReference || in_array($id, $arrClipboard['id']))) ? Image::getHtml('pasteafter_.gif') : '<a href="'.$this->addToUrl('act='.$arrClipboard['mode'].'&mode=1&pid='.$id.(!is_array($arrClipboard['id']) ? '&id='.$arrClipboard['id'] : '')).'" title="'.specialchars(sprintf($GLOBALS['TL_LANG'][$this->strTable]['pasteafter'][1], $id)).'" onclick="Backend.getScrollOffset()">'.$imagePasteAfter.'</a> ') : '';
$_buttons .= ($this->strTable != $table) ? '<a href="'.$this->addToUrl('act='.$arrClipboard['mode'].'&mode=2&pid='.$id.(!is_array($arrClipboard['id']) ? '&id='.$arrClipboard['id'] : '')).'" title="'.specialchars(sprintf($GLOBALS['TL_LANG'][$this->strTable]['pasteinto'][1], $id)).'" onclick="Backend.getScrollOffset()">'.$imagePasteInto.'</a> ' : '';
}
}
}
$return .= ($_buttons ?: ' ') . '</div><div style="clear:both"></div></li>';
// Add the records of the table itself
if ($table != $this->strTable)
{
$objChilds = $this->Database->prepare("SELECT id FROM " . $this->strTable . " WHERE pid=?" . ($blnHasSorting ? " ORDER BY sorting" : ''))
->execute($id);
if ($objChilds->numRows)
{
$ids = $objChilds->fetchEach('id');
for ($j=0, $c=count($ids); $j<$c; $j++)
{
$return .= $this->generateTree($this->strTable, $ids[$j], array('pp'=>$ids[($j-1)], 'nn'=>$ids[($j+1)]), $blnHasSorting, ($intMargin + $intSpacing + 20), $arrClipboard, false, ($j<(count($ids)-1) || !empty($childs)));
}
}
}
// Begin a new submenu
if (!$blnNoRecursion)
{
if (!empty($childs) && $session[$node][$id] == 1)
{
$return .= '<li class="parent" id="'.$node.'_'.$id.'"><ul class="level_'.$level.'">';
}
// Add the records of the parent table
if ($session[$node][$id] == 1)
{
if (is_array($childs))
{
for ($k=0, $c=count($childs); $k<$c; $k++)
{
$return .= $this->generateTree($table, $childs[$k], array('p'=>$childs[($k-1)], 'n'=>$childs[($k+1)]), $blnHasSorting, ($intMargin + $intSpacing), $arrClipboard, ((($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 5 && $childs[$k] == $arrClipboard['id']) || $blnCircularReference) ? true : false), ($blnProtected || $protectedPage));
}
}
}
// Close the submenu
if (!empty($childs) && $session[$node][$id] == 1)
{
$return .= '</ul></li>';
}
}
$this->Session->setData($session);
return $return;
}
/**
* Show header of the parent table and list all records of the current table
*
* @return string
*/
protected function parentView()
{
$blnClipboard = false;
$arrClipboard = $this->Session->get('CLIPBOARD');
$table = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 6) ? $this->ptable : $this->strTable;
$blnHasSorting = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['fields'][0] == 'sorting';
$blnMultiboard = false;
// Check clipboard
if (!empty($arrClipboard[$table]))
{
$blnClipboard = true;
$arrClipboard = $arrClipboard[$table];
if (is_array($arrClipboard['id']))
{
$blnMultiboard = true;
}
}
// Load the fonts to display the paste hint
Config::set('loadGoogleFonts', $blnClipboard);
// Load the language file and data container array of the parent table
System::loadLanguageFile($this->ptable);
$this->loadDataContainer($this->ptable);
$return = '
<div id="tl_buttons">' . (Input::get('nb') ? ' ' : ($this->ptable ? '
<a href="'.$this->getReferer(true, $this->ptable).'" class="header_back" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['backBTTitle']).'" accesskey="b" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG']['MSC']['backBT'].'</a>' : (isset($GLOBALS['TL_DCA'][$this->strTable]['config']['backlink']) ? '
<a href="contao/main.php?'.$GLOBALS['TL_DCA'][$this->strTable]['config']['backlink'].'" class="header_back" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['backBTTitle']).'" accesskey="b" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG']['MSC']['backBT'].'</a>' : ''))) . ' ' . ((Input::get('act') != 'select' && !$blnClipboard && !$GLOBALS['TL_DCA'][$this->strTable]['config']['closed'] && !$GLOBALS['TL_DCA'][$this->strTable]['config']['notCreatable']) ? '
<a href="'.$this->addToUrl(($blnHasSorting ? 'act=paste&mode=create' : 'act=create&mode=2&pid='.$this->intId)).'" class="header_new" title="'.specialchars($GLOBALS['TL_LANG'][$this->strTable]['new'][1]).'" accesskey="n" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG'][$this->strTable]['new'][0].'</a> ' : '') . ($blnClipboard ? '
<a href="'.$this->addToUrl('clipboard=1').'" class="header_clipboard" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['clearClipboard']).'" accesskey="x">'.$GLOBALS['TL_LANG']['MSC']['clearClipboard'].'</a> ' : $this->generateGlobalButtons()) . '
</div>' . Message::generate(true);
// Get all details of the parent record
$objParent = $this->Database->prepare("SELECT * FROM " . $this->ptable . " WHERE id=?")
->limit(1)
->execute(CURRENT_ID);
if ($objParent->numRows < 1)
{
return $return;
}
$return .= ((Input::get('act') == 'select') ? '
<form action="'.ampersand(Environment::get('request'), true).'" id="tl_select" class="tl_form'.((Input::get('act') == 'select') ? ' unselectable' : '').'" method="post" novalidate>
<div class="tl_formbody">
<input type="hidden" name="FORM_SUBMIT" value="tl_select">
<input type="hidden" name="REQUEST_TOKEN" value="'.REQUEST_TOKEN.'">' : '').($blnClipboard ? '
<div id="paste_hint">
<p>'.$GLOBALS['TL_LANG']['MSC']['selectNewPosition'].'</p>
</div>' : '').'
<div class="tl_listing_container parent_view">
<div class="tl_header click2edit toggle_select" onmouseover="Theme.hoverDiv(this,1)" onmouseout="Theme.hoverDiv(this,0)">';
// List all records of the child table
if (!Input::get('act') || Input::get('act') == 'paste' || Input::get('act') == 'select')
{
$this->import('BackendUser', 'User');
// Header
$imagePasteNew = Image::getHtml('new.gif', $GLOBALS['TL_LANG'][$this->strTable]['pastenew'][0]);
$imagePasteAfter = Image::getHtml('pasteafter.gif', $GLOBALS['TL_LANG'][$this->strTable]['pasteafter'][0]);
$imageEditHeader = Image::getHtml('header.gif', $GLOBALS['TL_LANG'][$this->strTable]['editheader'][0]);
$return .= '
<div class="tl_content_right">'.((Input::get('act') == 'select') ? '
<label for="tl_select_trigger" class="tl_select_label">'.$GLOBALS['TL_LANG']['MSC']['selectAll'].'</label> <input type="checkbox" id="tl_select_trigger" onclick="Backend.toggleCheckboxes(this)" class="tl_tree_checkbox">' : ($blnClipboard ? ' <a href="'.$this->addToUrl('act='.$arrClipboard['mode'].'&mode=2&pid='.$objParent->id . (!$blnMultiboard ? '&id='.$arrClipboard['id'] : '')).'" title="'.specialchars($GLOBALS['TL_LANG'][$this->strTable]['pasteafter'][0]).'" onclick="Backend.getScrollOffset()">'.$imagePasteAfter.'</a>' : ((!$GLOBALS['TL_DCA'][$this->ptable]['config']['notEditable'] && $this->User->canEditFieldsOf($this->ptable)) ? '
<a href="'.preg_replace('/&(amp;)?table=[^& ]*/i', (($this->ptable != '') ? '&table='.$this->ptable : ''), $this->addToUrl('act=edit')).'" class="edit" title="'.specialchars($GLOBALS['TL_LANG'][$this->strTable]['editheader'][1]).'">'.$imageEditHeader.'</a>' : '') . (($blnHasSorting && !$GLOBALS['TL_DCA'][$this->strTable]['config']['closed'] && !$GLOBALS['TL_DCA'][$this->strTable]['config']['notCreatable']) ? ' <a href="'.$this->addToUrl('act=create&mode=2&pid='.$objParent->id.'&id='.$this->intId).'" title="'.specialchars($GLOBALS['TL_LANG'][$this->strTable]['pastenew'][0]).'">'.$imagePasteNew.'</a>' : ''))) . '
</div>';
// Format header fields
$add = array();
$headerFields = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['headerFields'];
foreach ($headerFields as $v)
{
$_v = deserialize($objParent->$v);
// Translate UUIDs to paths
if ($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['inputType'] == 'fileTree')
{
$objFiles = FilesModel::findMultipleByUuids((array) $_v);
if ($objFiles !== null)
{
$_v = $objFiles->fetchEach('path');
}
}
if (is_array($_v))
{
$_v = implode(', ', $_v);
}
elseif ($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['inputType'] == 'checkbox' && !$GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['eval']['multiple'])
{
$_v = ($_v != '') ? $GLOBALS['TL_LANG']['MSC']['yes'] : $GLOBALS['TL_LANG']['MSC']['no'];
}
elseif ($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['eval']['rgxp'] == 'date')
{
$_v = $_v ? Date::parse(Config::get('dateFormat'), $_v) : '-';
}
elseif ($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['eval']['rgxp'] == 'time')
{
$_v = $_v ? Date::parse(Config::get('timeFormat'), $_v) : '-';
}
elseif ($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['eval']['rgxp'] == 'datim')
{
$_v = $_v ? Date::parse(Config::get('datimFormat'), $_v) : '-';
}
elseif ($v == 'tstamp')
{
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['dynamicPtable'])
{
$ptable = $GLOBALS['TL_DCA'][$this->strTable]['config']['ptable'];
$cond = ($ptable == 'tl_article') ? "(ptable=? OR ptable='')" : "ptable=?"; // backwards compatibility
$objMaxTstamp = $this->Database->prepare("SELECT MAX(tstamp) AS tstamp FROM " . $this->strTable . " WHERE pid=? AND $cond")
->execute($objParent->id, $ptable);
}
else
{
$objMaxTstamp = $this->Database->prepare("SELECT MAX(tstamp) AS tstamp FROM " . $this->strTable . " WHERE pid=?")
->execute($objParent->id);
}
if (!$objMaxTstamp->tstamp)
{
$objMaxTstamp->tstamp = $objParent->tstamp;
}
$_v = Date::parse(Config::get('datimFormat'), max($objParent->tstamp, $objMaxTstamp->tstamp));
}
elseif (isset($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['foreignKey']))
{
$arrForeignKey = explode('.', $GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['foreignKey'], 2);
$objLabel = $this->Database->prepare("SELECT " . $arrForeignKey[1] . " AS value FROM " . $arrForeignKey[0] . " WHERE id=?")
->limit(1)
->execute($_v);
if ($objLabel->numRows)
{
$_v = $objLabel->value;
}
}
elseif (is_array($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['reference'][$_v]))
{
$_v = $GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['reference'][$_v][0];
}
elseif (isset($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['reference'][$_v]))
{
$_v = $GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['reference'][$_v];
}
elseif ($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['eval']['isAssociative'] || array_is_assoc($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['options']))
{
$_v = $GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['options'][$_v];
}
elseif (is_array($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['options_callback']))
{
$strClass = $GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['options_callback'][0];
$strMethod = $GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['options_callback'][1];
$this->import($strClass);
$options_callback = $this->$strClass->$strMethod($this);
$_v = $options_callback[$_v];
}
elseif (is_callable($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['options_callback']))
{
$options_callback = $GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['options_callback']($this);
$_v = $options_callback[$_v];
}
// Add the sorting field
if ($_v != '')
{
if (isset($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['label']))
{
$key = is_array($GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['label']) ? $GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['label'][0] : $GLOBALS['TL_DCA'][$this->ptable]['fields'][$v]['label'];
}
else
{
$key = isset($GLOBALS['TL_LANG'][$this->ptable][$v][0]) ? $GLOBALS['TL_LANG'][$this->ptable][$v][0] : $v;
}
$add[$key] = $_v;
}
}
// Trigger the header_callback (see #3417)
if (is_array($GLOBALS['TL_DCA'][$table]['list']['sorting']['header_callback']))
{
$strClass = $GLOBALS['TL_DCA'][$table]['list']['sorting']['header_callback'][0];
$strMethod = $GLOBALS['TL_DCA'][$table]['list']['sorting']['header_callback'][1];
$this->import($strClass);
$add = $this->$strClass->$strMethod($add, $this);
}
elseif (is_callable($GLOBALS['TL_DCA'][$table]['list']['sorting']['header_callback']))
{
$add = $GLOBALS['TL_DCA'][$table]['list']['sorting']['header_callback']($add, $this);
}
// Output the header data
$return .= '
<table class="tl_header_table">';
foreach ($add as $k=>$v)
{
if (is_array($v))
{
$v = $v[0];
}
$return .= '
<tr>
<td><span class="tl_label">'.$k.':</span> </td>
<td>'.$v.'</td>
</tr>';
}
$return .= '
</table>
</div>';
$orderBy = array();
$firstOrderBy = array();
// Add all records of the current table
$query = "SELECT * FROM " . $this->strTable;
if (is_array($this->orderBy) && strlen($this->orderBy[0]))
{
$orderBy = $this->orderBy;
$firstOrderBy = preg_replace('/s+.*$/', '', $orderBy[0]);
// Order by the foreign key
if (isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$firstOrderBy]['foreignKey']))
{
$key = explode('.', $GLOBALS['TL_DCA'][$this->strTable]['fields'][$firstOrderBy]['foreignKey'], 2);
$query = "SELECT *, (SELECT ". $key[1] ." FROM ". $key[0] ." WHERE ". $this->strTable .".". $firstOrderBy ."=". $key[0] .".id) AS foreignKey FROM " . $this->strTable;
$orderBy[0] = 'foreignKey';
}
}
elseif (is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['fields']))
{
$orderBy = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['fields'];
$firstOrderBy = preg_replace('/s+.*$/', '', $orderBy[0]);
}
$arrProcedure = $this->procedure;
$arrValues = $this->values;
// Support empty ptable fields (backwards compatibility)
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['dynamicPtable'])
{
$arrProcedure[] = ($this->ptable == 'tl_article') ? "(ptable=? OR ptable='')" : "ptable=?";
$arrValues[] = $this->ptable;
}
// WHERE
if (!empty($arrProcedure))
{
$query .= " WHERE " . implode(' AND ', $arrProcedure);
}
if (!empty($this->root) && is_array($this->root))
{
$query .= (!empty($arrProcedure) ? " AND " : " WHERE ") . "id IN(" . implode(',', array_map('intval', $this->root)) . ")";
}
// ORDER BY
if (!empty($orderBy) && is_array($orderBy))
{
$query .= " ORDER BY " . implode(', ', $orderBy);
}
$objOrderByStmt = $this->Database->prepare($query);
// LIMIT
if (strlen($this->limit))
{
$arrLimit = explode(',', $this->limit);
$objOrderByStmt->limit($arrLimit[1], $arrLimit[0]);
}
$objOrderBy = $objOrderByStmt->execute($arrValues);
if ($objOrderBy->numRows < 1)
{
return $return . '
<p class="tl_empty_parent_view">'.$GLOBALS['TL_LANG']['MSC']['noResult'].'</p>
</div>';
}
// Call the child_record_callback
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['child_record_callback']) || is_callable($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['child_record_callback']))
{
$strGroup = '';
$blnIndent = false;
$intWrapLevel = 0;
$row = $objOrderBy->fetchAllAssoc();
// Make items sortable
if ($blnHasSorting)
{
$return .= '
<ul id="ul_' . CURRENT_ID . '">';
}
for ($i=0, $c=count($row); $i<$c; $i++)
{
$this->current[] = $row[$i]['id'];
$imagePasteAfter = Image::getHtml('pasteafter.gif', sprintf($GLOBALS['TL_LANG'][$this->strTable]['pasteafter'][1], $row[$i]['id']));
$imagePasteNew = Image::getHtml('new.gif', sprintf($GLOBALS['TL_LANG'][$this->strTable]['pastenew'][1], $row[$i]['id']));
// Decrypt encrypted value
foreach ($row[$i] as $k=>$v)
{
if ($GLOBALS['TL_DCA'][$table]['fields'][$k]['eval']['encrypt'])
{
$row[$i][$k] = Encryption::decrypt(deserialize($v));
}
}
// Make items sortable
if ($blnHasSorting)
{
$return .= '
<li id="li_' . $row[$i]['id'] . '">';
}
// Add the group header
if (!$GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['disableGrouping'] && $firstOrderBy != 'sorting')
{
$sortingMode = (count($orderBy) == 1 && $firstOrderBy == $orderBy[0] && $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['flag'] != '' && $GLOBALS['TL_DCA'][$this->strTable]['fields'][$firstOrderBy]['flag'] == '') ? $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['flag'] : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$firstOrderBy]['flag'];
$remoteNew = $this->formatCurrentValue($firstOrderBy, $row[$i][$firstOrderBy], $sortingMode);
$group = $this->formatGroupHeader($firstOrderBy, $remoteNew, $sortingMode, $row);
if ($group != $strGroup)
{
$return .= "nn" . '<div class="tl_content_header">'.$group.'</div>';
$strGroup = $group;
}
}
$blnWrapperStart = in_array($row[$i]['type'], $GLOBALS['TL_WRAPPERS']['start']);
$blnWrapperSeparator = in_array($row[$i]['type'], $GLOBALS['TL_WRAPPERS']['separator']);
$blnWrapperStop = in_array($row[$i]['type'], $GLOBALS['TL_WRAPPERS']['stop']);
// Closing wrappers
if ($blnWrapperStop)
{
if (--$intWrapLevel < 1)
{
$blnIndent = false;
}
}
$return .= '
<div class="tl_content'.($blnWrapperStart ? ' wrapper_start' : '').($blnWrapperSeparator ? ' wrapper_separator' : '').($blnWrapperStop ? ' wrapper_stop' : '').($blnIndent ? ' indent indent_'.$intWrapLevel : '').(($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['child_record_class'] != '') ? ' ' . $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['child_record_class'] : '').(($i%2 == 0) ? ' even' : ' odd').' click2edit toggle_select" onmouseover="Theme.hoverDiv(this,1)" onmouseout="Theme.hoverDiv(this,0)">
<div class="tl_content_right">';
// Opening wrappers
if ($blnWrapperStart)
{
if (++$intWrapLevel > 0)
{
$blnIndent = true;
}
}
// Edit multiple
if (Input::get('act') == 'select')
{
$return .= '<input type="checkbox" name="IDS[]" id="ids_'.$row[$i]['id'].'" class="tl_tree_checkbox" value="'.$row[$i]['id'].'">';
}
// Regular buttons
else
{
$return .= $this->generateButtons($row[$i], $this->strTable, $this->root, false, null, $row[($i-1)]['id'], $row[($i+1)]['id']);
// Sortable table
if ($blnHasSorting)
{
// Create new button
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['closed'] && !$GLOBALS['TL_DCA'][$this->strTable]['config']['notCreatable'])
{
$return .= ' <a href="'.$this->addToUrl('act=create&mode=1&pid='.$row[$i]['id'].'&id='.$objParent->id).'" title="'.specialchars(sprintf($GLOBALS['TL_LANG'][$this->strTable]['pastenew'][1], $row[$i]['id'])).'">'.$imagePasteNew.'</a>';
}
// Prevent circular references
if ($blnClipboard && $arrClipboard['mode'] == 'cut' && $row[$i]['id'] == $arrClipboard['id'] || $blnMultiboard && $arrClipboard['mode'] == 'cutAll' && in_array($row[$i]['id'], $arrClipboard['id']))
{
$return .= ' ' . Image::getHtml('pasteafter_.gif');
}
// Copy/move multiple
elseif ($blnMultiboard)
{
$return .= ' <a href="'.$this->addToUrl('act='.$arrClipboard['mode'].'&mode=1&pid='.$row[$i]['id']).'" title="'.specialchars(sprintf($GLOBALS['TL_LANG'][$this->strTable]['pasteafter'][1], $row[$i]['id'])).'" onclick="Backend.getScrollOffset()">'.$imagePasteAfter.'</a>';
}
// Paste buttons
elseif ($blnClipboard)
{
$return .= ' <a href="'.$this->addToUrl('act='.$arrClipboard['mode'].'&mode=1&pid='.$row[$i]['id'].'&id='.$arrClipboard['id']).'" title="'.specialchars(sprintf($GLOBALS['TL_LANG'][$this->strTable]['pasteafter'][1], $row[$i]['id'])).'" onclick="Backend.getScrollOffset()">'.$imagePasteAfter.'</a>';
}
// Drag handle
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notSortable'])
{
$return .= ' ' . Image::getHtml('drag.gif', '', 'class="drag-handle" title="' . sprintf($GLOBALS['TL_LANG'][$this->strTable]['cut'][1], $row[$i]['id']) . '"');
}
}
}
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['child_record_callback']))
{
$strClass = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['child_record_callback'][0];
$strMethod = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['child_record_callback'][1];
$this->import($strClass);
$return .= '</div>'.$this->$strClass->$strMethod($row[$i]).'</div>';
}
elseif (is_callable($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['child_record_callback']))
{
$return .= '</div>'.$GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['child_record_callback']($row[$i]).'</div>';
}
// Make items sortable
if ($blnHasSorting)
{
$return .= '
</li>';
}
}
}
}
// Make items sortable
if ($blnHasSorting && !$GLOBALS['TL_DCA'][$this->strTable]['config']['notSortable'] && Input::get('act') != 'select')
{
$return .= '
</ul>
<script>
Backend.makeParentViewSortable("ul_' . CURRENT_ID . '");
</script>';
}
$return .= '
</div>';
// Close form
if (Input::get('act') == 'select')
{
// Submit buttons
$arrButtons = array();
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notDeletable'])
{
$arrButtons['delete'] = '<input type="submit" name="delete" id="delete" class="tl_submit" accesskey="d" onclick="return confirm(''.$GLOBALS['TL_LANG']['MSC']['delAllConfirm'].'')" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['deleteSelected']).'">';
}
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notSortable'])
{
$arrButtons['cut'] = '<input type="submit" name="cut" id="cut" class="tl_submit" accesskey="x" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['moveSelected']).'">';
}
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notCopyable'])
{
$arrButtons['copy'] = '<input type="submit" name="copy" id="copy" class="tl_submit" accesskey="c" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['copySelected']).'">';
}
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notEditable'])
{
$arrButtons['override'] = '<input type="submit" name="override" id="override" class="tl_submit" accesskey="v" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['overrideSelected']).'">';
$arrButtons['edit'] = '<input type="submit" name="edit" id="edit" class="tl_submit" accesskey="s" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['editSelected']).'">';
}
// Call the buttons_callback (see #4691)
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['select']['buttons_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['select']['buttons_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$arrButtons = $this->{$callback[0]}->{$callback[1]}($arrButtons, $this);
}
elseif (is_callable($callback))
{
$arrButtons = $callback($arrButtons, $this);
}
}
}
$return .= '
<div class="tl_formbody_submit" style="text-align:right">
<div class="tl_submit_container">
' . implode(' ', $arrButtons) . '
</div>
</div>
</div>
</form>';
}
return $return;
}
/**
* List all records of the current table and return them as HTML string
*
* @return string
*/
protected function listView()
{
$return = '';
$table = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 6) ? $this->ptable : $this->strTable;
$orderBy = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['fields'];
$firstOrderBy = preg_replace('/s+.*$/', '', $orderBy[0]);
if (is_array($this->orderBy) && $this->orderBy[0] != '')
{
$orderBy = $this->orderBy;
$firstOrderBy = $this->firstOrderBy;
}
$query = "SELECT * FROM " . $this->strTable;
if (!empty($this->procedure))
{
$query .= " WHERE " . implode(' AND ', $this->procedure);
}
if (!empty($this->root) && is_array($this->root))
{
$query .= (!empty($this->procedure) ? " AND " : " WHERE ") . "id IN(" . implode(',', array_map('intval', $this->root)) . ")";
}
if (is_array($orderBy) && $orderBy[0] != '')
{
foreach ($orderBy as $k=>$v)
{
list($key, $direction) = explode(' ', $v, 2);
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$key]['eval']['findInSet'])
{
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$key]['options_callback']))
{
$strClass = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$key]['options_callback'][0];
$strMethod = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$key]['options_callback'][1];
$this->import($strClass);
$keys = $this->$strClass->$strMethod($this);
}
elseif (is_callable($GLOBALS['TL_DCA'][$this->strTable]['fields'][$key]['options_callback']))
{
$keys = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$key]['options_callback']($this);
}
else
{
$keys = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$key]['options'];
}
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$key]['eval']['isAssociative'] || array_is_assoc($keys))
{
$keys = array_keys($keys);
}
$orderBy[$k] = $this->Database->findInSet($v, $keys);
}
elseif (in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$key]['flag'], array(5, 6, 7, 8, 9, 10)))
{
$orderBy[$k] = "CAST($key AS SIGNED)" . ($direction ? " $direction" : ""); // see #5503
}
}
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 3)
{
$firstOrderBy = 'pid';
$showFields = $GLOBALS['TL_DCA'][$table]['list']['label']['fields'];
$query .= " ORDER BY (SELECT " . $showFields[0] . " FROM " . $this->ptable . " WHERE " . $this->ptable . ".id=" . $this->strTable . ".pid), " . implode(', ', $orderBy);
// Set the foreignKey so that the label is translated (also for backwards compatibility)
if ($GLOBALS['TL_DCA'][$table]['fields']['pid']['foreignKey'] == '')
{
$GLOBALS['TL_DCA'][$table]['fields']['pid']['foreignKey'] = $this->ptable . '.' . $showFields[0];
}
// Remove the parent field from label fields
array_shift($showFields);
$GLOBALS['TL_DCA'][$table]['list']['label']['fields'] = $showFields;
}
else
{
$query .= " ORDER BY " . implode(', ', $orderBy);
}
}
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 1 && ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['flag'] % 2) == 0)
{
$query .= " DESC";
}
$objRowStmt = $this->Database->prepare($query);
if ($this->limit != '')
{
$arrLimit = explode(',', $this->limit);
$objRowStmt->limit($arrLimit[1], $arrLimit[0]);
}
$objRow = $objRowStmt->execute($this->values);
$this->bid = ($return != '') ? $this->bid : 'tl_buttons';
// Display buttos
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['closed'] || !empty($GLOBALS['TL_DCA'][$this->strTable]['list']['global_operations']))
{
$return .= '
<div id="'.$this->bid.'">'.((Input::get('act') == 'select' || $this->ptable) ? '
<a href="'.$this->getReferer(true, $this->ptable).'" class="header_back" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['backBTTitle']).'" accesskey="b" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG']['MSC']['backBT'].'</a> ' : (isset($GLOBALS['TL_DCA'][$this->strTable]['config']['backlink']) ? '
<a href="contao/main.php?'.$GLOBALS['TL_DCA'][$this->strTable]['config']['backlink'].'" class="header_back" title="'.specialchars($GLOBALS['TL_LANG']['MSC']['backBTTitle']).'" accesskey="b" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG']['MSC']['backBT'].'</a> ' : '')) . ((Input::get('act') != 'select' && !$GLOBALS['TL_DCA'][$this->strTable]['config']['closed'] && !$GLOBALS['TL_DCA'][$this->strTable]['config']['notCreatable']) ? '
<a href="'.(($this->ptable != '') ? $this->addToUrl('act=create' . (($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] < 4) ? '&mode=2' : '') . '&pid=' . $this->intId) : $this->addToUrl('act=create')).'" class="header_new" title="'.specialchars($GLOBALS['TL_LANG'][$this->strTable]['new'][1]).'" accesskey="n" onclick="Backend.getScrollOffset()">'.$GLOBALS['TL_LANG'][$this->strTable]['new'][0].'</a> ' : '') . $this->generateGlobalButtons() . '
</div>' . Message::generate(true);
}
// Return "no records found" message
if ($objRow->numRows < 1)
{
$return .= '
<p class="tl_empty">'.$GLOBALS['TL_LANG']['MSC']['noResult'].'</p>';
}
// List records
else
{
$result = $objRow->fetchAllAssoc();
$return .= ((Input::get('act') == 'select') ? '
<form action="'.ampersand(Environment::get('request'), true).'" id="tl_select" class="tl_form'.((Input::get('act') == 'select') ? ' unselectable' : '').'" method="post" novalidate>
<div class="tl_formbody">
<input type="hidden" name="FORM_SUBMIT" value="tl_select">
<input type="hidden" name="REQUEST_TOKEN" value="'.REQUEST_TOKEN.'">' : '').'
<div class="tl_listing_container list_view">'.((Input::get('act') == 'select') ? '
<div class="tl_select_trigger">
<label for="tl_select_trigger" class="tl_select_label">'.$GLOBALS['TL_LANG']['MSC']['selectAll'].'</label> <input type="checkbox" id="tl_select_trigger" onclick="Backend.toggleCheckboxes(this)" class="tl_tree_checkbox">
</div>' : '').'
<table class="tl_listing' . ($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['showColumns'] ? ' showColumns' : '') . '">';
// Automatically add the "order by" field as last column if we do not have group headers
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['showColumns'])
{
$blnFound = false;
// Extract the real key and compare it to $firstOrderBy
foreach ($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['fields'] as $f)
{
if (strpos($f, ':') !== false)
{
list($f) = explode(':', $f, 2);
}
if ($firstOrderBy == $f)
{
$blnFound = true;
break;
}
}
if (!$blnFound)
{
$GLOBALS['TL_DCA'][$this->strTable]['list']['label']['fields'][] = $firstOrderBy;
}
}
// Generate the table header if the "show columns" option is active
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['showColumns'])
{
$return .= '
<tr>';
foreach ($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['fields'] as $f)
{
if (strpos($f, ':') !== false)
{
list($f) = explode(':', $f, 2);
}
$return .= '
<th class="tl_folder_tlist col_' . $f . (($f == $firstOrderBy) ? ' ordered_by' : '') . '">'.(is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$f]['label']) ? $GLOBALS['TL_DCA'][$this->strTable]['fields'][$f]['label'][0] : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$f]['label']).'</th>';
}
$return .= '
<th class="tl_folder_tlist tl_right_nowrap"> </th>
</tr>';
}
// Process result and add label and buttons
$remoteCur = false;
$groupclass = 'tl_folder_tlist';
$eoCount = -1;
foreach ($result as $row)
{
$args = array();
$this->current[] = $row['id'];
$showFields = $GLOBALS['TL_DCA'][$table]['list']['label']['fields'];
// Label
foreach ($showFields as $k=>$v)
{
// Decrypt the value
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$v]['eval']['encrypt'])
{
$row[$v] = Encryption::decrypt(deserialize($row[$v]));
}
if (strpos($v, ':') !== false)
{
list($strKey, $strTable) = explode(':', $v);
list($strTable, $strField) = explode('.', $strTable);
$objRef = $this->Database->prepare("SELECT " . $strField . " FROM " . $strTable . " WHERE id=?")
->limit(1)
->execute($row[$strKey]);
$args[$k] = $objRef->numRows ? $objRef->$strField : '';
}
elseif (in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$v]['flag'], array(5, 6, 7, 8, 9, 10)))
{
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$v]['eval']['rgxp'] == 'date')
{
$args[$k] = $row[$v] ? Date::parse(Config::get('dateFormat'), $row[$v]) : '-';
}
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$v]['eval']['rgxp'] == 'time')
{
$args[$k] = $row[$v] ? Date::parse(Config::get('timeFormat'), $row[$v]) : '-';
}
else
{
$args[$k] = $row[$v] ? Date::parse(Config::get('datimFormat'), $row[$v]) : '-';
}
}
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$v]['inputType'] == 'checkbox' && !$GLOBALS['TL_DCA'][$this->strTable]['fields'][$v]['eval']['multiple'])
{
$args[$k] = ($row[$v] != '') ? $GLOBALS['TL_LANG']['MSC']['yes'] : $GLOBALS['TL_LANG']['MSC']['no'];
}
else
{
$row_v = deserialize($row[$v]);
if (is_array($row_v))
{
$args_k = array();
foreach ($row_v as $option)
{
$args_k[] = $GLOBALS['TL_DCA'][$table]['fields'][$v]['reference'][$option] ?: $option;
}
$args[$k] = implode(', ', $args_k);
}
elseif (isset($GLOBALS['TL_DCA'][$table]['fields'][$v]['reference'][$row[$v]]))
{
$args[$k] = is_array($GLOBALS['TL_DCA'][$table]['fields'][$v]['reference'][$row[$v]]) ? $GLOBALS['TL_DCA'][$table]['fields'][$v]['reference'][$row[$v]][0] : $GLOBALS['TL_DCA'][$table]['fields'][$v]['reference'][$row[$v]];
}
elseif (($GLOBALS['TL_DCA'][$table]['fields'][$v]['eval']['isAssociative'] || array_is_assoc($GLOBALS['TL_DCA'][$table]['fields'][$v]['options'])) && isset($GLOBALS['TL_DCA'][$table]['fields'][$v]['options'][$row[$v]]))
{
$args[$k] = $GLOBALS['TL_DCA'][$table]['fields'][$v]['options'][$row[$v]];
}
else
{
$args[$k] = $row[$v];
}
}
}
// Shorten the label it if it is too long
$label = vsprintf($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['format'] ?: '%s', $args);
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['maxCharacters'] > 0 && $GLOBALS['TL_DCA'][$this->strTable]['list']['label']['maxCharacters'] < strlen(strip_tags($label)))
{
$label = trim(StringUtil::substrHtml($label, $GLOBALS['TL_DCA'][$this->strTable]['list']['label']['maxCharacters'])) . ' …';
}
// Remove empty brackets (), [], {}, <> and empty tags from the label
$label = preg_replace('/( *) ?|[ *] ?|{ *} ?|< *> ?/', '', $label);
$label = preg_replace('/<[^>]+>s*</[^>]+>/', '', $label);
// Build the sorting groups
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] > 0)
{
$current = $row[$firstOrderBy];
$orderBy = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['fields'];
$sortingMode = (count($orderBy) == 1 && $firstOrderBy == $orderBy[0] && $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['flag'] != '' && $GLOBALS['TL_DCA'][$this->strTable]['fields'][$firstOrderBy]['flag'] == '') ? $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['flag'] : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$firstOrderBy]['flag'];
$remoteNew = $this->formatCurrentValue($firstOrderBy, $current, $sortingMode);
// Add the group header
if (!$GLOBALS['TL_DCA'][$this->strTable]['list']['label']['showColumns'] && !$GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['disableGrouping'] && ($remoteNew != $remoteCur || $remoteCur === false))
{
$eoCount = -1;
$group = $this->formatGroupHeader($firstOrderBy, $remoteNew, $sortingMode, $row);
$remoteCur = $remoteNew;
$return .= '
<tr>
<td colspan="2" class="'.$groupclass.'">'.$group.'</td>
</tr>';
$groupclass = 'tl_folder_list';
}
}
$return .= '
<tr class="'.((++$eoCount % 2 == 0) ? 'even' : 'odd').' click2edit toggle_select" onmouseover="Theme.hoverRow(this,1)" onmouseout="Theme.hoverRow(this,0)">
';
$colspan = 1;
// Call the label_callback ($row, $label, $this)
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['label_callback']) || is_callable($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['label_callback']))
{
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['label_callback']))
{
$strClass = $GLOBALS['TL_DCA'][$this->strTable]['list']['label']['label_callback'][0];
$strMethod = $GLOBALS['TL_DCA'][$this->strTable]['list']['label']['label_callback'][1];
$this->import($strClass);
$args = $this->$strClass->$strMethod($row, $label, $this, $args);
}
elseif (is_callable($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['label_callback']))
{
$args = $GLOBALS['TL_DCA'][$this->strTable]['list']['label']['label_callback']($row, $label, $this, $args);
}
// Handle strings and arrays (backwards compatibility)
if (!$GLOBALS['TL_DCA'][$this->strTable]['list']['label']['showColumns'])
{
$label = is_array($args) ? implode(' ', $args) : $args;
}
elseif (!is_array($args))
{
$args = array($args);
$colspan = count($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['fields']);
}
}
// Show columns
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['showColumns'])
{
foreach ($args as $j=>$arg)
{
$return .= '<td colspan="' . $colspan . '" class="tl_file_list col_' . $GLOBALS['TL_DCA'][$this->strTable]['list']['label']['fields'][$j] . (($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['fields'][$j] == $firstOrderBy) ? ' ordered_by' : '') . '">' . ($arg ?: '-') . '</td>';
}
}
else
{
$return .= '<td class="tl_file_list">' . $label . '</td>';
}
// Buttons ($row, $table, $root, $blnCircularReference, $childs, $previous, $next)
$return .= ((Input::get('act') == 'select') ? '
<td class="tl_file_list tl_right_nowrap"><input type="checkbox" name="IDS[]" id="ids_'.$row['id'].'" class="tl_tree_checkbox" value="'.$row['id'].'"></td>' : '
<td class="tl_file_list tl_right_nowrap">'.$this->generateButtons($row, $this->strTable, $this->root).'</td>') . '
</tr>';
}
// Close the table
$return .= '
</table>
</div>';
// Close the form
if (Input::get('act') == 'select')
{
// Submit buttons
$arrButtons = array();
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notDeletable'])
{
$arrButtons['delete'] = '<input type="submit" name="delete" id="delete" class="tl_submit" accesskey="d" onclick="return confirm(''.$GLOBALS['TL_LANG']['MSC']['delAllConfirm'].'')" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['deleteSelected']).'">';
}
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notCopyable'])
{
$arrButtons['copy'] = '<input type="submit" name="copy" id="copy" class="tl_submit" accesskey="c" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['copySelected']).'">';
}
if (!$GLOBALS['TL_DCA'][$this->strTable]['config']['notEditable'])
{
$arrButtons['override'] = '<input type="submit" name="override" id="override" class="tl_submit" accesskey="v" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['overrideSelected']).'">';
$arrButtons['edit'] = '<input type="submit" name="edit" id="edit" class="tl_submit" accesskey="s" value="'.specialchars($GLOBALS['TL_LANG']['MSC']['editSelected']).'">';
}
// Call the buttons_callback (see #4691)
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['select']['buttons_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['select']['buttons_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$arrButtons = $this->{$callback[0]}->{$callback[1]}($arrButtons, $this);
}
elseif (is_callable($callback))
{
$arrButtons = $callback($arrButtons, $this);
}
}
}
$return .= '
<div class="tl_formbody_submit" style="text-align:right">
<div class="tl_submit_container">
' . implode(' ', $arrButtons) . '
</div>
</div>
</div>
</form>';
}
}
return $return;
}
/**
* Build the sort panel and return it as string
*
* @return string
*/
protected function panel()
{
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['panelLayout'] == '')
{
return '';
}
$intFilterPanel = 0;
$arrPanels = array();
foreach (trimsplit(';', $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['panelLayout']) as $strPanel)
{
$panels = '';
$arrSubPanels = trimsplit(',', $strPanel);
foreach ($arrSubPanels as $strSubPanel)
{
$panel = '';
// Regular panels
if ($strSubPanel == 'search' || $strSubPanel == 'limit' || $strSubPanel == 'sort')
{
$panel = $this->{$strSubPanel . 'Menu'}();
}
// Multiple filter subpanels can be defined to split the fields across panels
elseif ($strSubPanel == 'filter')
{
$panel = $this->{$strSubPanel . 'Menu'}(++$intFilterPanel);
}
// Call the panel_callback
else
{
$arrCallback = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['panel_callback'][$strSubPanel];
if (is_array($arrCallback))
{
$this->import($arrCallback[0]);
$panel = $this->{$arrCallback[0]}->{$arrCallback[1]}($this);
}
elseif (is_callable($arrCallback))
{
$panel = $arrCallback($this);
}
}
// Add the panel if it is not empty
if ($panel != '')
{
$panels = $panel . $panels;
}
}
// Add the group if it is not empty
if ($panels != '')
{
$arrPanels[] = $panels;
}
}
if (empty($arrPanels))
{
return '';
}
if (Input::post('FORM_SUBMIT') == 'tl_filters')
{
$this->reload();
}
$return = '';
$intTotal = count($arrPanels);
$intLast = $intTotal - 1;
for ($i=0; $i<$intTotal; $i++)
{
$submit = '';
if ($i == $intLast)
{
$submit = '
<div class="tl_submit_panel tl_subpanel">
<input type="image" name="filter" id="filter" src="' . TL_FILES_URL . 'system/themes/' . Backend::getTheme() . '/images/reload.gif" class="tl_img_submit" title="' . specialchars($GLOBALS['TL_LANG']['MSC']['applyTitle']) . '" alt="' . specialchars($GLOBALS['TL_LANG']['MSC']['apply']) . '">
</div>';
}
$return .= '
<div class="tl_panel">' . $submit . $arrPanels[$i] . '
<div class="clear"></div>
</div>';
}
$return = '
<form action="'.ampersand(Environment::get('request'), true).'" class="tl_form" method="post">
<div class="tl_formbody">
<input type="hidden" name="FORM_SUBMIT" value="tl_filters">
<input type="hidden" name="REQUEST_TOKEN" value="'.REQUEST_TOKEN.'">
' . $return . '
</div>
</form>
';
return $return;
}
/**
* Return a search form that allows to search results using regular expressions
*
* @return string
*/
protected function searchMenu()
{
$searchFields = array();
$session = $this->Session->getData();
// Get search fields
foreach ($GLOBALS['TL_DCA'][$this->strTable]['fields'] as $k=>$v)
{
if ($v['search'])
{
$searchFields[] = $k;
}
}
// Return if there are no search fields
if (empty($searchFields))
{
return '';
}
// Store search value in the current session
if (Input::post('FORM_SUBMIT') == 'tl_filters')
{
$session['search'][$this->strTable]['value'] = '';
$session['search'][$this->strTable]['field'] = Input::post('tl_field', true);
// Make sure the regular expression is valid
if (Input::postRaw('tl_value') != '')
{
try
{
$this->Database->prepare("SELECT * FROM " . $this->strTable . " WHERE " . Input::post('tl_field', true) . " REGEXP ?")
->limit(1)
->execute(Input::postRaw('tl_value'));
$session['search'][$this->strTable]['value'] = Input::postRaw('tl_value');
}
catch (Exception $e) {}
}
$this->Session->setData($session);
}
// Set the search value from the session
elseif ($session['search'][$this->strTable]['value'] != '')
{
$strPattern = "CAST(%s AS CHAR) REGEXP ?";
if (substr(Config::get('dbCollation'), -3) == '_ci')
{
$strPattern = "LOWER(CAST(%s AS CHAR)) REGEXP LOWER(?)";
}
$fld = $session['search'][$this->strTable]['field'];
if (isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$fld]['foreignKey']))
{
list($t, $f) = explode('.', $GLOBALS['TL_DCA'][$this->strTable]['fields'][$fld]['foreignKey']);
$this->procedure[] = "(" . sprintf($strPattern, $fld) . " OR " . sprintf($strPattern, "(SELECT $f FROM $t WHERE $t.id={$this->strTable}.$fld)") . ")";
$this->values[] = $session['search'][$this->strTable]['value'];
}
else
{
$this->procedure[] = sprintf($strPattern, $fld);
}
$this->values[] = $session['search'][$this->strTable]['value'];
}
$options_sorter = array();
foreach ($searchFields as $field)
{
$option_label = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label'][0] ?: (is_array($GLOBALS['TL_LANG']['MSC'][$field]) ? $GLOBALS['TL_LANG']['MSC'][$field][0] : $GLOBALS['TL_LANG']['MSC'][$field]);
$options_sorter[utf8_romanize($option_label).'_'.$field] = ' <option value="'.specialchars($field).'"'.(($field == $session['search'][$this->strTable]['field']) ? ' selected="selected"' : '').'>'.$option_label.'</option>';
}
// Sort by option values
$options_sorter = natcaseksort($options_sorter);
$active = ($session['search'][$this->strTable]['value'] != '') ? true : false;
return '
<div class="tl_search tl_subpanel">
<strong>' . $GLOBALS['TL_LANG']['MSC']['search'] . ':</strong>
<select name="tl_field" class="tl_select' . ($active ? ' active' : '') . '">
'.implode("n", $options_sorter).'
</select>
<span> = </span>
<input type="search" name="tl_value" class="tl_text' . ($active ? ' active' : '') . '" value="'.specialchars($session['search'][$this->strTable]['value']).'">
</div>';
}
/**
* Return a select menu that allows to sort results by a particular field
*
* @return string
*/
protected function sortMenu()
{
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] != 2 && $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] != 4)
{
return '';
}
$sortingFields = array();
// Get sorting fields
foreach ($GLOBALS['TL_DCA'][$this->strTable]['fields'] as $k=>$v)
{
if ($v['sorting'])
{
$sortingFields[] = $k;
}
}
// Return if there are no sorting fields
if (empty($sortingFields))
{
return '';
}
$this->bid = 'tl_buttons_a';
$session = $this->Session->getData();
$orderBy = $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['fields'];
$firstOrderBy = preg_replace('/s+.*$/', '', $orderBy[0]);
// Add PID to order fields
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 3 && $this->Database->fieldExists('pid', $this->strTable))
{
array_unshift($orderBy, 'pid');
}
// Set sorting from user input
if (Input::post('FORM_SUBMIT') == 'tl_filters')
{
$strSort = Input::post('tl_sort');
// Validate the user input (thanks to aulmn) (see #4971)
if (in_array($strSort, $sortingFields))
{
$session['sorting'][$this->strTable] = in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$strSort]['flag'], array(2, 4, 6, 8, 10, 12)) ? "$strSort DESC" : $strSort;
$this->Session->setData($session);
}
}
// Overwrite the "orderBy" value with the session value
elseif (strlen($session['sorting'][$this->strTable]))
{
$overwrite = preg_quote(preg_replace('/s+.*$/', '', $session['sorting'][$this->strTable]), '/');
$orderBy = array_diff($orderBy, preg_grep('/^'.$overwrite.'/i', $orderBy));
array_unshift($orderBy, $session['sorting'][$this->strTable]);
$this->firstOrderBy = $overwrite;
$this->orderBy = $orderBy;
}
$options_sorter = array();
// Sorting fields
foreach ($sortingFields as $field)
{
$options_label = strlen(($lbl = is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label']) ? $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label'][0] : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label'])) ? $lbl : $GLOBALS['TL_LANG']['MSC'][$field];
if (is_array($options_label))
{
$options_label = $options_label[0];
}
$options_sorter[$options_label] = ' <option value="'.specialchars($field).'"'.((!strlen($session['sorting'][$this->strTable]) && $field == $firstOrderBy || $field == str_replace(' DESC', '', $session['sorting'][$this->strTable])) ? ' selected="selected"' : '').'>'.$options_label.'</option>';
}
// Sort by option values
uksort($options_sorter, 'strcasecmp');
return '
<div class="tl_sorting tl_subpanel">
<strong>' . $GLOBALS['TL_LANG']['MSC']['sortBy'] . ':</strong>
<select name="tl_sort" id="tl_sort" class="tl_select">
'.implode("n", $options_sorter).'
</select>
</div>';
}
/**
* Return a select menu to limit results
*
* @param boolean $blnOptional
*
* @return string
*/
protected function limitMenu($blnOptional=false)
{
$session = $this->Session->getData();
$filter = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 4) ? $this->strTable.'_'.CURRENT_ID : $this->strTable;
$fields = '';
// Set limit from user input
if (Input::post('FORM_SUBMIT') == 'tl_filters' || Input::post('FORM_SUBMIT') == 'tl_filters_limit')
{
$strLimit = Input::post('tl_limit');
if ($strLimit == 'tl_limit')
{
unset($session['filter'][$filter]['limit']);
}
else
{
// Validate the user input (thanks to aulmn) (see #4971)
if ($strLimit == 'all' || preg_match('/^[0-9]+,[0-9]+$/', $strLimit))
{
$session['filter'][$filter]['limit'] = $strLimit;
}
}
$this->Session->setData($session);
if (Input::post('FORM_SUBMIT') == 'tl_filters_limit')
{
$this->reload();
}
}
// Set limit from table configuration
else
{
$this->limit = ($session['filter'][$filter]['limit'] != '') ? (($session['filter'][$filter]['limit'] == 'all') ? null : $session['filter'][$filter]['limit']) : '0,' . Config::get('resultsPerPage');
$arrProcedure = $this->procedure;
$arrValues = $this->values;
$query = "SELECT COUNT(*) AS count FROM " . $this->strTable;
if (!empty($this->root) && is_array($this->root))
{
$arrProcedure[] = 'id IN(' . implode(',', $this->root) . ')';
}
// Support empty ptable fields (backwards compatibility)
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['dynamicPtable'])
{
$arrProcedure[] = ($this->ptable == 'tl_article') ? "(ptable=? OR ptable='')" : "ptable=?";
$arrValues[] = $this->ptable;
}
if (!empty($arrProcedure))
{
$query .= " WHERE " . implode(' AND ', $arrProcedure);
}
$objTotal = $this->Database->prepare($query)->execute($arrValues);
$this->total = $objTotal->count;
$options_total = 0;
$blnIsMaxResultsPerPage = false;
// Overall limit
if ($this->total > Config::get('maxResultsPerPage') && ($this->limit === null || preg_replace('/^.*,/', '', $this->limit) == Config::get('maxResultsPerPage')))
{
if ($this->limit === null)
{
$this->limit = '0,' . Config::get('maxResultsPerPage');
}
$blnIsMaxResultsPerPage = true;
Config::set('resultsPerPage', Config::get('maxResultsPerPage'));
$session['filter'][$filter]['limit'] = Config::get('maxResultsPerPage');
}
$options = '';
// Build options
if ($this->total > 0)
{
$options = '';
$options_total = ceil($this->total / Config::get('resultsPerPage'));
// Reset limit if other parameters have decreased the number of results
if ($this->limit !== null && ($this->limit == '' || preg_replace('/,.*$/', '', $this->limit) > $this->total))
{
$this->limit = '0,'.Config::get('resultsPerPage');
}
// Build options
for ($i=0; $i<$options_total; $i++)
{
$this_limit = ($i*Config::get('resultsPerPage')).','.Config::get('resultsPerPage');
$upper_limit = ($i*Config::get('resultsPerPage')+Config::get('resultsPerPage'));
if ($upper_limit > $this->total)
{
$upper_limit = $this->total;
}
$options .= '
<option value="'.$this_limit.'"' . Widget::optionSelected($this->limit, $this_limit) . '>'.($i*Config::get('resultsPerPage')+1).' - '.$upper_limit.'</option>';
}
if (!$blnIsMaxResultsPerPage)
{
$options .= '
<option value="all"' . Widget::optionSelected($this->limit, null) . '>'.$GLOBALS['TL_LANG']['MSC']['filterAll'].'</option>';
}
}
// Return if there is only one page
if ($blnOptional && ($this->total < 1 || $options_total < 2))
{
return '';
}
$fields = '
<select name="tl_limit" class="tl_select' . (($session['filter'][$filter]['limit'] != 'all' && $this->total > Config::get('resultsPerPage')) ? ' active' : '') . '" onchange="this.form.submit()">
<option value="tl_limit">'.$GLOBALS['TL_LANG']['MSC']['filterRecords'].'</option>'.$options.'
</select> ';
}
return '
<div class="tl_limit tl_subpanel">
<strong>' . $GLOBALS['TL_LANG']['MSC']['showOnly'] . ':</strong> '.$fields.'
</div>';
}
/**
* Generate the filter panel and return it as HTML string
*
* @param integer $intFilterPanel
*
* @return string
*/
protected function filterMenu($intFilterPanel)
{
$fields = '';
$this->bid = 'tl_buttons_a';
$sortingFields = array();
$session = $this->Session->getData();
$filter = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 4) ? $this->strTable.'_'.CURRENT_ID : $this->strTable;
// Get the sorting fields
foreach ($GLOBALS['TL_DCA'][$this->strTable]['fields'] as $k=>$v)
{
if (intval($v['filter']) == $intFilterPanel)
{
$sortingFields[] = $k;
}
}
// Return if there are no sorting fields
if (empty($sortingFields))
{
return '';
}
// Set filter from user input
if (Input::post('FORM_SUBMIT') == 'tl_filters')
{
foreach ($sortingFields as $field)
{
if (Input::post($field, true) != 'tl_'.$field)
{
$session['filter'][$filter][$field] = Input::post($field, true);
}
else
{
unset($session['filter'][$filter][$field]);
}
}
$this->Session->setData($session);
}
// Set filter from table configuration
else
{
foreach ($sortingFields as $field)
{
if (isset($session['filter'][$filter][$field]))
{
// Sort by day
if (in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['flag'], array(5, 6)))
{
if ($session['filter'][$filter][$field] == '')
{
$this->procedure[] = $field . "=''";
}
else
{
$objDate = new Date($session['filter'][$filter][$field]);
$this->procedure[] = $field . ' BETWEEN ? AND ?';
$this->values[] = $objDate->dayBegin;
$this->values[] = $objDate->dayEnd;
}
}
// Sort by month
elseif (in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['flag'], array(7, 8)))
{
if ($session['filter'][$filter][$field] == '')
{
$this->procedure[] = $field . "=''";
}
else
{
$objDate = new Date($session['filter'][$filter][$field]);
$this->procedure[] = $field . ' BETWEEN ? AND ?';
$this->values[] = $objDate->monthBegin;
$this->values[] = $objDate->monthEnd;
}
}
// Sort by year
elseif (in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['flag'], array(9, 10)))
{
if ($session['filter'][$filter][$field] == '')
{
$this->procedure[] = $field . "=''";
}
else
{
$objDate = new Date($session['filter'][$filter][$field]);
$this->procedure[] = $field . ' BETWEEN ? AND ?';
$this->values[] = $objDate->yearBegin;
$this->values[] = $objDate->yearEnd;
}
}
// Manual filter
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['multiple'])
{
// CSV lists (see #2890)
if (isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['csv']))
{
$this->procedure[] = $this->Database->findInSet('?', $field, true);
$this->values[] = $session['filter'][$filter][$field];
}
else
{
$this->procedure[] = $field . ' LIKE ?';
$this->values[] = '%"' . $session['filter'][$filter][$field] . '"%';
}
}
// Other sort algorithm
else
{
$this->procedure[] = $field . '=?';
$this->values[] = $session['filter'][$filter][$field];
}
}
}
}
// Add sorting options
foreach ($sortingFields as $cnt=>$field)
{
$arrValues = array();
$arrProcedure = array();
if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 4)
{
$arrProcedure[] = 'pid=?';
$arrValues[] = CURRENT_ID;
}
if (!empty($this->root) && is_array($this->root))
{
$arrProcedure[] = "id IN(" . implode(',', array_map('intval', $this->root)) . ")";
}
// Check for a static filter (see #4719)
if (!empty($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['filter']) && is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['filter']))
{
foreach ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['filter'] as $fltr)
{
$arrProcedure[] = $fltr[0];
$arrValues[] = $fltr[1];
}
}
// Support empty ptable fields (backwards compatibility)
if ($GLOBALS['TL_DCA'][$this->strTable]['config']['dynamicPtable'])
{
$arrProcedure[] = ($this->ptable == 'tl_article') ? "(ptable=? OR ptable='')" : "ptable=?";
$arrValues[] = $this->ptable;
}
$objFields = $this->Database->prepare("SELECT DISTINCT " . $field . " FROM " . $this->strTable . ((is_array($arrProcedure) && strlen($arrProcedure[0])) ? ' WHERE ' . implode(' AND ', $arrProcedure) : ''))
->execute($arrValues);
// Begin select menu
$fields .= '
<select name="'.$field.'" id="'.$field.'" class="tl_select' . (isset($session['filter'][$filter][$field]) ? ' active' : '') . '">
<option value="tl_'.$field.'">'.(is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label']) ? $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label'][0] : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label']).'</option>
<option value="tl_'.$field.'">---</option>';
if ($objFields->numRows)
{
$options = $objFields->fetchEach($field);
// Sort by day
if (in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['flag'], array(5, 6)))
{
($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['flag'] == 6) ? rsort($options) : sort($options);
foreach ($options as $k=>$v)
{
if ($v == '')
{
$options[$v] = '-';
}
else
{
$options[$v] = Date::parse(Config::get('dateFormat'), $v);
}
unset($options[$k]);
}
}
// Sort by month
elseif (in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['flag'], array(7, 8)))
{
($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['flag'] == 8) ? rsort($options) : sort($options);
foreach ($options as $k=>$v)
{
if ($v == '')
{
$options[$v] = '-';
}
else
{
$options[$v] = date('Y-m', $v);
$intMonth = (date('m', $v) - 1);
if (isset($GLOBALS['TL_LANG']['MONTHS'][$intMonth]))
{
$options[$v] = $GLOBALS['TL_LANG']['MONTHS'][$intMonth] . ' ' . date('Y', $v);
}
}
unset($options[$k]);
}
}
// Sort by year
elseif (in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['flag'], array(9, 10)))
{
($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['flag'] == 10) ? rsort($options) : sort($options);
foreach ($options as $k=>$v)
{
if ($v == '')
{
$options[$v] = '-';
}
else
{
$options[$v] = date('Y', $v);
}
unset($options[$k]);
}
}
// Manual filter
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['multiple'])
{
$moptions = array();
// TODO: find a more effective solution
foreach($options as $option)
{
// CSV lists (see #2890)
if (isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['csv']))
{
$doptions = trimsplit($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['csv'], $option);
}
else
{
$doptions = deserialize($option);
}
if (is_array($doptions))
{
$moptions = array_merge($moptions, $doptions);
}
}
$options = $moptions;
}
$options = array_unique($options);
$options_callback = array();
// Call the options_callback
if ((is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options_callback']) || is_callable($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options_callback'])) && !$GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['reference'])
{
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options_callback']))
{
$strClass = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options_callback'][0];
$strMethod = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options_callback'][1];
$this->import($strClass);
$options_callback = $this->$strClass->$strMethod($this);
}
elseif (is_callable($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options_callback']))
{
$options_callback = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options_callback']($this);
}
// Sort options according to the keys of the callback array
$options = array_intersect(array_keys($options_callback), $options);
}
$options_sorter = array();
$blnDate = in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['flag'], array(5, 6, 7, 8, 9, 10));
// Options
foreach ($options as $kk=>$vv)
{
$value = $blnDate ? $kk : $vv;
// Options callback
if (!empty($options_callback) && is_array($options_callback))
{
$vv = $options_callback[$vv];
}
// Replace the ID with the foreign key
elseif (isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['foreignKey']))
{
$key = explode('.', $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['foreignKey'], 2);
$objParent = $this->Database->prepare("SELECT " . $key[1] . " AS value FROM " . $key[0] . " WHERE id=?")
->limit(1)
->execute($vv);
if ($objParent->numRows)
{
$vv = $objParent->value;
}
}
// Replace boolean checkbox value with "yes" and "no"
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['isBoolean'] || ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['inputType'] == 'checkbox' && !$GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['multiple']))
{
$vv = ($vv != '') ? $GLOBALS['TL_LANG']['MSC']['yes'] : $GLOBALS['TL_LANG']['MSC']['no'];
}
// Get the name of the parent record (see #2703)
elseif ($field == 'pid')
{
$this->loadDataContainer($this->ptable);
$showFields = $GLOBALS['TL_DCA'][$this->ptable]['list']['label']['fields'];
if (!$showFields[0])
{
$showFields[0] = 'id';
}
$objShowFields = $this->Database->prepare("SELECT " . $showFields[0] . " FROM ". $this->ptable . " WHERE id=?")
->limit(1)
->execute($vv);
if ($objShowFields->numRows)
{
$vv = $objShowFields->{$showFields[0]};
}
}
$option_label = '';
// Use reference array
if (isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['reference']))
{
$option_label = is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['reference'][$vv]) ? $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['reference'][$vv][0] : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['reference'][$vv];
}
// Associative array
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['isAssociative'] || array_is_assoc($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options']))
{
$option_label = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options'][$vv];
}
// No empty options allowed
if (!strlen($option_label))
{
$option_label = $vv ?: '-';
}
$options_sorter[' <option value="' . specialchars($value) . '"' . ((isset($session['filter'][$filter][$field]) && $value == $session['filter'][$filter][$field]) ? ' selected="selected"' : '').'>'.$option_label.'</option>'] = utf8_romanize($option_label);
}
// Sort by option values
if (!$blnDate)
{
natcasesort($options_sorter);
if (in_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['flag'], array(2, 4, 12)))
{
$options_sorter = array_reverse($options_sorter, true);
}
}
$fields .= "n" . implode("n", array_keys($options_sorter));
}
// End select menu
$fields .= '
</select> ';
// Force a line-break after six elements (see #3777)
if ((($cnt + 1) % 6) == 0)
{
$fields .= '<br>';
}
}
return '
<div class="tl_filter tl_subpanel">
<strong>' . $GLOBALS['TL_LANG']['MSC']['filter'] . ':</strong> ' . $fields . '
</div>';
}
/**
* Return a pagination menu to browse results
*
* @return string
*/
protected function paginationMenu()
{
$session = $this->Session->getData();
$filter = ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'] == 4) ? $this->strTable.'_'.CURRENT_ID : $this->strTable;
list($offset, $limit) = explode(',', $this->limit);
// Set the limit filter based on the page number
if (isset($_GET['lp']))
{
$lp = intval(Input::get('lp')) - 1;
if ($lp >= 0 && $lp < ceil($this->total / $limit))
{
$session['filter'][$filter]['limit'] = ($lp * $limit) . ',' . $limit;
$this->Session->setData($session);
}
$this->redirect(preg_replace('/&(amp;)?lp=[^&]+/i', '', Environment::get('request')));
}
if ($limit) // see #6923
{
Input::setGet('lp', $offset / $limit + 1);
}
$objPagination = new Pagination($this->total, $limit, 7, 'lp', new BackendTemplate('be_pagination'), true);
return $objPagination->generate();
}
/**
* Return the formatted group header as string
*
* @param string $field
* @param mixed $value
* @param integer $mode
*
* @return string
*/
protected function formatCurrentValue($field, $value, $mode)
{
$remoteNew = $value; // see #3861
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['inputType'] == 'checkbox' && !$GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['multiple'])
{
$remoteNew = ($value != '') ? ucfirst($GLOBALS['TL_LANG']['MSC']['yes']) : ucfirst($GLOBALS['TL_LANG']['MSC']['no']);
}
elseif (isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['foreignKey']))
{
$key = explode('.', $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['foreignKey'], 2);
$objParent = $this->Database->prepare("SELECT " . $key[1] . " AS value FROM " . $key[0] . " WHERE id=?")
->limit(1)
->execute($value);
if ($objParent->numRows)
{
$remoteNew = $objParent->value;
}
}
elseif (in_array($mode, array(1, 2)))
{
$remoteNew = ($value != '') ? ucfirst(utf8_substr($value , 0, 1)) : '-';
}
elseif (in_array($mode, array(3, 4)))
{
if (!isset($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['length']))
{
$GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['length'] = 2;
}
$remoteNew = ($value != '') ? ucfirst(utf8_substr($value , 0, $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['length'])) : '-';
}
elseif (in_array($mode, array(5, 6)))
{
$remoteNew = ($value != '') ? Date::parse(Config::get('dateFormat'), $value) : '-';
}
elseif (in_array($mode, array(7, 8)))
{
$remoteNew = ($value != '') ? date('Y-m', $value) : '-';
$intMonth = ($value != '') ? (date('m', $value) - 1) : '-';
if (isset($GLOBALS['TL_LANG']['MONTHS'][$intMonth]))
{
$remoteNew = ($value != '') ? $GLOBALS['TL_LANG']['MONTHS'][$intMonth] . ' ' . date('Y', $value) : '-';
}
}
elseif (in_array($mode, array(9, 10)))
{
$remoteNew = ($value != '') ? date('Y', $value) : '-';
}
else
{
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['inputType'] == 'checkbox' && !$GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['multiple'])
{
$remoteNew = ($value != '') ? $field : '';
}
elseif (is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['reference']))
{
$remoteNew = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['reference'][$value];
}
elseif ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['isAssociative'] || array_is_assoc($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options']))
{
$remoteNew = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options'][$value];
}
else
{
$remoteNew = $value;
}
if (is_array($remoteNew))
{
$remoteNew = $remoteNew[0];
}
if (empty($remoteNew))
{
$remoteNew = '-';
}
}
return $remoteNew;
}
/**
* Return the formatted group header as string
*
* @param string $field
* @param mixed $value
* @param integer $mode
* @param array $row
*
* @return string
*/
protected function formatGroupHeader($field, $value, $mode, $row)
{
static $lookup = array();
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['isAssociative'] || array_is_assoc($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options']))
{
$group = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options'][$value];
}
elseif (is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options_callback']))
{
if (!isset($lookup[$field]))
{
$strClass = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options_callback'][0];
$strMethod = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['options_callback'][1];
$this->import($strClass);
$lookup[$field] = $this->$strClass->$strMethod($this);
}
$group = $lookup[$field][$value];
}
else
{
$group = is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['reference'][$value]) ? $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['reference'][$value][0] : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['reference'][$value];
}
if (empty($group))
{
$group = is_array($GLOBALS['TL_LANG'][$this->strTable][$value]) ? $GLOBALS['TL_LANG'][$this->strTable][$value][0] : $GLOBALS['TL_LANG'][$this->strTable][$value];
}
if (empty($group))
{
$group = $value;
if ($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['eval']['isBoolean'] && $value != '-')
{
$group = is_array($GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label']) ? $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label'][0] : $GLOBALS['TL_DCA'][$this->strTable]['fields'][$field]['label'];
}
}
// Call the group callback ($group, $sortingMode, $firstOrderBy, $row, $this)
if (is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['group_callback']))
{
$strClass = $GLOBALS['TL_DCA'][$this->strTable]['list']['label']['group_callback'][0];
$strMethod = $GLOBALS['TL_DCA'][$this->strTable]['list']['label']['group_callback'][1];
$this->import($strClass);
$group = $this->$strClass->$strMethod($group, $mode, $field, $row, $this);
}
elseif (is_callable($GLOBALS['TL_DCA'][$this->strTable]['list']['label']['group_callback']))
{
$group = $GLOBALS['TL_DCA'][$this->strTable]['list']['label']['group_callback']($group, $mode, $field, $row, $this);
}
return $group;
}
}