Integrate filtering system with objectManager, use it for applications
This commit is contained in:
committed by
Chris Morgan
parent
b2adeab95a
commit
4f792a28ba
@@ -11,6 +11,7 @@ require_once(BASE."include/util.php");
|
||||
require_once(BASE."include/mail.php");
|
||||
require_once(BASE."include/maintainer.php");
|
||||
require_once(BASE."include/tableve.php");
|
||||
require_once(BASE."include/db_filter_ui.php");
|
||||
|
||||
define("PLATINUM_RATING", "Platinum");
|
||||
define("GOLD_RATING", "Gold");
|
||||
@@ -939,11 +940,18 @@ class Application {
|
||||
return 'appId';
|
||||
}
|
||||
|
||||
public static function objectGetEntries($sState, $iRows = 0, $iStart = 0, $sOrderBy = "appId", $bAscending = TRUE)
|
||||
public static function objectGetEntries($sState, $iRows = 0, $iStart = 0, $sOrderBy = "appId", $bAscending = TRUE, $oFilters = null)
|
||||
{
|
||||
$sLimit = "";
|
||||
$sOrdering = $bAscending ? "ASC" : "DESC";
|
||||
|
||||
$sExtraTables = '';
|
||||
$sWhereFilter = $oFilters ? $oFilters->getWhereClause() : '';
|
||||
if($sWhereFilter)
|
||||
{
|
||||
$sExtraTables = ',appVersion';
|
||||
$sWhereFilter = " AND appVersion.appId = appFamily.appId AND $sWhereFilter";
|
||||
}
|
||||
/* Should we add a limit clause to the query? */
|
||||
if($iRows || $iStart)
|
||||
{
|
||||
@@ -955,10 +963,10 @@ class Application {
|
||||
$iRows = application::objectGetEntriesCount($sState);
|
||||
}
|
||||
|
||||
$sQuery = "SELECT appFamily.*, vendor.vendorName AS vendorName FROM appFamily, vendor WHERE
|
||||
$sQuery = "SELECT DISTINCT(appFamily.appId), appFamily.*, vendor.vendorName AS vendorName FROM appFamily, vendor$sExtraTables WHERE
|
||||
appFamily.vendorId = vendor.vendorId
|
||||
AND
|
||||
appFamily.state = '?'";
|
||||
appFamily.state = '?'$sWhereFilter";
|
||||
|
||||
if($sState != 'accepted' && !application::canEdit())
|
||||
{
|
||||
@@ -997,6 +1005,14 @@ class Application {
|
||||
return $hResult;
|
||||
}
|
||||
|
||||
public static function objectGetFilterInfo()
|
||||
{
|
||||
$oFilter = new FilterInterface();
|
||||
|
||||
$oFilter->AddFilterInfo('appVersion.rating', 'Rating', array(FILTER_EQUALS, FILTER_LESS_THAN, FILTER_GREATER_THAN), FILTER_VALUES_ENUM, array('Platinum', 'Gold', 'Silver', 'Bronze', 'Garbage'));
|
||||
return $oFilter;
|
||||
}
|
||||
|
||||
public static function objectGetSortableFields()
|
||||
{
|
||||
return array('submitTime', 'appName', 'appId', 'userName', 'vendorName');
|
||||
@@ -1112,23 +1128,31 @@ class Application {
|
||||
}
|
||||
}
|
||||
|
||||
public static function objectGetEntriesCount($sState)
|
||||
public static function objectGetEntriesCount($sState, $oFilters = null)
|
||||
{
|
||||
$sExtraTables = '';
|
||||
$sWhereFilter = $oFilters ? $oFilters->getWhereClause() : '';
|
||||
if($sWhereFilter)
|
||||
{
|
||||
$sExtraTables = ',appVersion';
|
||||
$sWhereFilter = " AND appVersion.appId = appFamily.appId AND $sWhereFilter";
|
||||
}
|
||||
|
||||
if($sState != 'accepted' && !application::canEdit())
|
||||
{
|
||||
/* Without edit rights users can only resubmit their rejected entries */
|
||||
if(!$bRejected)
|
||||
return FALSE;
|
||||
|
||||
$sQuery = "SELECT COUNT(appId) as count FROM appFamily WHERE
|
||||
$sQuery = "SELECT COUNT(DISTINCT(appFamily.appId)) as count FROM appFamily$sExtraTables WHERE
|
||||
submitterId = '?'
|
||||
AND
|
||||
state = '?'";
|
||||
appFamily.state = '?'$sWhereFilter";
|
||||
$hResult = query_parameters($sQuery, $_SESSION['current']->iUserId,
|
||||
$sState);
|
||||
} else
|
||||
{
|
||||
$sQuery = "SELECT COUNT(appId) as count FROM appFamily WHERE state = '?'";
|
||||
$sQuery = "SELECT COUNT(DISTINCT(appFamily.appId)) as count FROM appFamily$sExtraTables WHERE appFamily.state = '?'$sWhereFilter";
|
||||
$hResult = query_parameters($sQuery, $sState);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ define('FILTER_GREATER_THAN', 3);
|
||||
define('FILTER_LESS_THAN', 4);
|
||||
define('FILTER_NOT_EQUALS', 5);
|
||||
define('FILTER_NOT_LIKE', 6);
|
||||
define('FILTER_ENUM', 7);
|
||||
|
||||
/* A filter as part of an SQL query, such as something = 'somevalue' */
|
||||
class Filter
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
|
||||
require_once('db_filter.php');
|
||||
|
||||
define(FILTER_VALUES_NORMAL, 1);
|
||||
define(FILTER_VALUES_ENUM, 2);
|
||||
define(FILTER_VALUES_BOOL, 3);
|
||||
|
||||
/* Info describing an available filter: what column it applies to,
|
||||
and what comparison options are available */
|
||||
class FilterInfo
|
||||
@@ -17,12 +21,22 @@ class FilterInfo
|
||||
private $sColumn;
|
||||
private $sDisplayName;
|
||||
private $aTypes; // Available filters for this column
|
||||
private $iValueType; // Normal, enum ...
|
||||
private $aValueTypeData; // List of enums
|
||||
private $aValueTypeDataDisplay; // Optional display names for enums
|
||||
|
||||
public function FilterInfo($sColumn, $sDisplayName, $aTypes)
|
||||
public function FilterInfo($sColumn, $sDisplayName, $aTypes, $iValueType = FILTER_VALUES_NORMAL, $aValueTypeData = array(), $aValueTypeDisplay = array())
|
||||
{
|
||||
$this->sColumn = $sColumn;
|
||||
$this->sDisplayName = $sDisplayName;
|
||||
$this->aTypes = $aTypes;
|
||||
$this->iValueType = $iValueType;
|
||||
$this->aValueTypeData = $aValueTypeData;
|
||||
|
||||
if(sizeof($aValueTypeData) && !sizeof($aValueTypeDisplay))
|
||||
$this->aValueTypeDataDisplay = $aValueTypeData;
|
||||
else
|
||||
$this->aValueTypeDataDisplay = $aValueTypeDisplay;
|
||||
}
|
||||
|
||||
public function getColumn()
|
||||
@@ -35,6 +49,21 @@ class FilterInfo
|
||||
return $this->sDisplayName;
|
||||
}
|
||||
|
||||
public function getValueType()
|
||||
{
|
||||
return $this->iValueType;
|
||||
}
|
||||
|
||||
public function getValueTypeData()
|
||||
{
|
||||
return $this->aValueTypeData;
|
||||
}
|
||||
|
||||
public function getValueTypeDataDisplay()
|
||||
{
|
||||
return $this->aValueTypeDataDisplay;
|
||||
}
|
||||
|
||||
public function getTypes()
|
||||
{
|
||||
return $this->aTypes;
|
||||
@@ -65,11 +94,15 @@ class FilterInterface
|
||||
{
|
||||
private $aFilterInfo;
|
||||
private $oFilterSet;
|
||||
private $aEscapeChars;
|
||||
private $aEscapeCharsWith;
|
||||
|
||||
public function FilterInterface($sTableName = '')
|
||||
{
|
||||
$this->aFilterInfo = array();
|
||||
$this->oFilterSet = new FilterSet(mysql_real_escape_string($sTableName));
|
||||
$this->oFilterSet = new FilterSet(query_escape_string($sTableName));
|
||||
$this->aEscapeChars = array('.');
|
||||
$this->aEscapeCharsWith = array('-');
|
||||
}
|
||||
|
||||
public function AddFilterObject(Filter $oFilter)
|
||||
@@ -83,15 +116,53 @@ class FilterInterface
|
||||
}
|
||||
|
||||
/* Convenience function to add a filter option */
|
||||
public function AddFilterInfo($sColumn, $sDisplayName, $aTypes)
|
||||
public function AddFilterInfo($sColumn, $sDisplayName, $aTypes, $iValueType = VALUE_TYPE_NORMAL, $aValueTypeData = array(), $aValueTypeDisplay = array())
|
||||
{
|
||||
$this->aFilterInfo[$sColumn] = new FilterInfo($sColumn, $sDisplayName, $aTypes);
|
||||
$this->aFilterInfo[$sColumn] = new FilterInfo($sColumn, $sDisplayName, $aTypes, $iValueType, $aValueTypeData, $aValueTypeDisplay);
|
||||
}
|
||||
|
||||
/* We can't use some special chars in variable names, such as '.' */
|
||||
public function escapeChars($sIn)
|
||||
{
|
||||
return str_replace($this->aEscapeChars, $this->aEscapeCharsWith, $sIn);
|
||||
}
|
||||
|
||||
public function unescapeChars($sIn)
|
||||
{
|
||||
return str_replace($this->aEscapeWith, $this->aEscape, $sIn);
|
||||
}
|
||||
|
||||
public function getUrlElement($iId, Filter $oFilter)
|
||||
{
|
||||
$sColumn = $this->escapeChars($oFilter->getColumn());
|
||||
$oColumn = $this->aFilterInfo[$sColumn];
|
||||
|
||||
$sId = $iId;
|
||||
|
||||
$shEditor = "&i{$sColumn}Op$sId={$oFilter->getOperatorId()}";
|
||||
$shEditor .= "&s{$sColumn}Data$sId={$oFilter->getData()}";
|
||||
|
||||
return $shEditor;
|
||||
}
|
||||
|
||||
public function getHiddenInputTag($iId, Filter $oFilter)
|
||||
{
|
||||
$sColumn = $this->escapeChars($oFilter->getColumn());
|
||||
$oColumn = $this->aFilterInfo[$sColumn];
|
||||
|
||||
$sId = $iId;
|
||||
|
||||
$shEditor = "<input type=\"hidden\" name=\"i{$sColumn}Op$sId\" value=\"{$oFilter->getOperatorId()}\">";
|
||||
$shEditor .= "<input type=\"hidden\" name=\"s{$sColumn}Data$sId\" value=\"{$oFilter->getData()}\" />";
|
||||
|
||||
return $shEditor;
|
||||
}
|
||||
|
||||
|
||||
public function getItemEditor($iId, Filter $oFilter)
|
||||
{
|
||||
$sColumn = $oFilter->getColumn();
|
||||
$oColumn = $this->aFilterInfo[$sColumn];
|
||||
$sColumn = $this->escapeChars($oFilter->getColumn());
|
||||
$oColumn = $this->aFilterInfo[$oFilter->getColumn()];
|
||||
|
||||
$sId = ($iId == -1) ? '' : $iId;
|
||||
$shEditor = $oColumn->getDisplayName();
|
||||
@@ -121,7 +192,90 @@ class FilterInterface
|
||||
|
||||
$shEditor .= '</select> ';
|
||||
|
||||
switch($oColumn->getValueType())
|
||||
{
|
||||
case FILTER_VALUES_NORMAL:
|
||||
$shEditor .= "<input type='text' value=\"{$oFilter->getData()}\" name='s{$sColumn}Data$sId' size='30' />";
|
||||
break;
|
||||
case FILTER_VALUES_ENUM:
|
||||
$shEditor .= $this->getEnumEditor($oColumn, $oFilter, $sId);
|
||||
break;
|
||||
}
|
||||
|
||||
return $shEditor;
|
||||
}
|
||||
|
||||
public function getEnumEditor($oColumn, $oFilter, $sId)
|
||||
{
|
||||
$sColumn = $this->escapeChars($oFilter->getColumn());
|
||||
$aOptions = $oColumn->getValueTypeData();
|
||||
$aOptionNames = $oColumn->getValueTypeDataDisplay();
|
||||
|
||||
$sData = $oFilter->getData();
|
||||
|
||||
$shEditor .= "<select name=\"s{$sColumn}Data$sId\">";
|
||||
|
||||
if($sData)
|
||||
$shEditor .= "<option value=\"\">-- remove --</option>";
|
||||
else
|
||||
$shEditor .= "<option value=\"\">-- select --</option>";
|
||||
|
||||
for($i = 0; $i < sizeof($aOptions); $i++)
|
||||
{
|
||||
$sOption = $aOptions[$i];
|
||||
$sSelected = '';
|
||||
if($sData == $sOption)
|
||||
$sSelected = ' selected="selected"';
|
||||
$shEditor .= "<option value=\"$sOption\"$sSelected>{$aOptionNames[$i]}</option>";
|
||||
}
|
||||
|
||||
$shEditor .= "</select>";
|
||||
|
||||
return $shEditor;
|
||||
}
|
||||
|
||||
/* Get filter data formatted to fit in a URL */
|
||||
public function getUrlData()
|
||||
{
|
||||
$shEditor = '';
|
||||
$aCounts = array();
|
||||
|
||||
foreach($this->oFilterSet->getFilters() as $oFilter)
|
||||
{
|
||||
$sColumn = $oFilter->getColumn();
|
||||
|
||||
if(!array_key_exists($sColumn, $aCounts))
|
||||
$aCounts[$sColumn] = 0;
|
||||
|
||||
$shEditor .= $this->getUrlElement($aCounts[$sColumn], $oFilter);
|
||||
|
||||
$shEditor .= '<br />';
|
||||
|
||||
$aCounts[$sColumn]++;
|
||||
}
|
||||
|
||||
return $shEditor;
|
||||
}
|
||||
|
||||
/* Get a list of hidden input tags to preserve form data */
|
||||
public function getHiddenFormData()
|
||||
{
|
||||
$shEditor = '';
|
||||
$aCounts = array();
|
||||
|
||||
foreach($this->oFilterSet->getFilters() as $oFilter)
|
||||
{
|
||||
$sColumn = $oFilter->getColumn();
|
||||
|
||||
if(!array_key_exists($sColumn, $aCounts))
|
||||
$aCounts[$sColumn] = 0;
|
||||
|
||||
$shEditor .= $this->getHiddenInputTag($aCounts[$sColumn], $oFilter);
|
||||
|
||||
$shEditor .= '<br />';
|
||||
|
||||
$aCounts[$sColumn]++;
|
||||
}
|
||||
|
||||
return $shEditor;
|
||||
}
|
||||
@@ -131,16 +285,18 @@ class FilterInterface
|
||||
$shEditor = '';
|
||||
$aCounts = array();
|
||||
|
||||
$shEditor .= 'Add new filter<br />';
|
||||
$shEditor .= '<b>Add new filter</b> <i>(You don’t have to fill out all rows.)</i><br />';
|
||||
foreach($this->aFilterInfo as $oOption)
|
||||
{
|
||||
$oDummyFilter = new Filter($oOption->getColumn(), 0, '');
|
||||
$aTypes = $oOption->getTypes();
|
||||
|
||||
$shEditor .= $this->getItemEditor(-1, $oDummyFilter);
|
||||
$shEditor .= '<br />';
|
||||
}
|
||||
|
||||
if(sizeof($this->oFilterSet->getFilters()))
|
||||
$shEditor .= '<br />Active filters<br />';
|
||||
$shEditor .= '<br /><b>Active filters</b><br />';
|
||||
foreach($this->oFilterSet->getFilters() as $oFilter)
|
||||
{
|
||||
$sColumn = $oFilter->getColumn();
|
||||
@@ -167,23 +323,26 @@ class FilterInterface
|
||||
{
|
||||
$aReturn = array();
|
||||
|
||||
for($i = 0; array_key_exists('i'.$oOption->getColumn()."Op$i", $aClean); $i++)
|
||||
for($i = 0; array_key_exists('i'.$this->escapeChars($oOption->getColumn())."Op$i", $aClean); $i++)
|
||||
{
|
||||
$sData = mysql_real_escape_string($aClean["s{$oOption->getColumn()}Data$i"]);
|
||||
$iOp = $aClean["i{$oOption->getColumn()}Op$i"];
|
||||
$sColumn = $this->escapeChars($oOption->getColumn());
|
||||
$sData = query_escape_string($aClean["s{$sColumn}Data$i"]);
|
||||
$iOp = $aClean["i{$sColumn}Op$i"];
|
||||
|
||||
if(!$iOp)
|
||||
continue;
|
||||
|
||||
$oFilter = new Filter($oOption->getColumn(), $iOp, $sData);
|
||||
|
||||
$aReturn[] = $oFilter;
|
||||
}
|
||||
|
||||
if(array_key_exists('i'.$oOption->getColumn()."Op", $aClean))
|
||||
if(array_key_exists('i'.$this->escapeChars($oOption->getColumn())."Op", $aClean))
|
||||
{
|
||||
$sColumn = $this->escapeChars($oOption->getColumn());
|
||||
$i = sizeof($aReturn);
|
||||
$sData = $aClean["s{$oOption->getColumn()}Data"];
|
||||
$iOp = $aClean["i{$oOption->getColumn()}Op"];
|
||||
$sData = $aClean["s{$sColumn}Data"];
|
||||
$iOp = $aClean["i{$sColumn}Op"];
|
||||
|
||||
if($iOp)
|
||||
{
|
||||
@@ -208,12 +367,12 @@ class FilterInterface
|
||||
|
||||
public function loadTable($sTableName)
|
||||
{
|
||||
$this->oFilterSet->loadTable(mysql_real_escape_string($sTableName));
|
||||
$this->oFilterSet->loadTable($sTableName);
|
||||
}
|
||||
|
||||
public function saveTable($sTableName)
|
||||
{
|
||||
$this->oFilterSet->saveTable(mysql_real_escape_string($sTableName));
|
||||
$this->oFilterSet->saveTable($sTableName);
|
||||
}
|
||||
|
||||
public function getFilterCount()
|
||||
@@ -221,6 +380,11 @@ class FilterInterface
|
||||
return $this->oFilterSet->getFilterCount();
|
||||
}
|
||||
|
||||
public function getWhereClause()
|
||||
{
|
||||
return $this->oFilterSet->getWhereClause();
|
||||
}
|
||||
|
||||
public function getTable($sTable, $iLimit = 0)
|
||||
{
|
||||
$hResult = $this->oFilterSet->getMatchedItems($sTable, $iLimit);
|
||||
|
||||
@@ -14,6 +14,7 @@ class ObjectManager
|
||||
private $sReturnTo;
|
||||
private $sReturnToTitle; /* Used to preserve the title when processing entries from a queue list, for instance */
|
||||
private $oMultiPage;
|
||||
private $oFilters; /* Currently active filters in table view */
|
||||
private $oTableRow;
|
||||
private $oSortInfo; /* Contains sort info used when displaying tables */
|
||||
private $oObject; /* Store an instance of the object of the class
|
||||
@@ -63,6 +64,12 @@ class ObjectManager
|
||||
return $this->sState;
|
||||
}
|
||||
|
||||
public function getFilterInfoFromInput($aClean)
|
||||
{
|
||||
if($this->oFilters)
|
||||
$this->oFilters->readInput($aClean);
|
||||
}
|
||||
|
||||
public function setState($sState)
|
||||
{
|
||||
/* Ensure that the given state is valid */
|
||||
@@ -126,6 +133,7 @@ class ObjectManager
|
||||
$this->oMultiPage = new MultiPage(FALSE);
|
||||
$this->oTableRow = new OMTableRow(null);
|
||||
$this->sState = 'accepted';
|
||||
$this->oFilters = $this->getOptionalSetting('objectGetFilterInfo', FALSE);
|
||||
|
||||
// initialize the common responses array
|
||||
$this->aCommonResponses = array();
|
||||
@@ -278,6 +286,8 @@ class ObjectManager
|
||||
if(!$this->oSortInfo->sCurrentSort)
|
||||
$this->oSortInfo->sCurrentSort = $this->getOptionalSetting('objectGetDefaultSort', '');
|
||||
|
||||
$this->handleFilterControls($aClean);
|
||||
|
||||
/* query the class for its entries */
|
||||
/* We pass in queue states to tell the object */
|
||||
/* if we are requesting a list of its queued objects or */
|
||||
@@ -286,10 +296,16 @@ class ObjectManager
|
||||
$this->oMultiPage->iItemsPerPage,
|
||||
$this->oMultiPage->iLowerLimit,
|
||||
$this->oSortInfo->sCurrentSort,
|
||||
$this->oSortInfo->bAscending);
|
||||
$this->oSortInfo->bAscending,
|
||||
$this->oFilters);
|
||||
|
||||
/* did we get any entries? */
|
||||
if(!$hResult || query_num_rows($hResult) == 0)
|
||||
{
|
||||
if($this->oFilters->getFilterCount())
|
||||
{
|
||||
echo '<center>No matches found</center>';
|
||||
} else
|
||||
{
|
||||
switch($this->getQueueString($this->getIsQueue(), $this->sState == 'rejected'))
|
||||
{
|
||||
@@ -304,6 +320,7 @@ class ObjectManager
|
||||
"present</center>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if($this->GetOptionalSetting("objectShowAddEntry", FALSE))
|
||||
{
|
||||
@@ -1236,6 +1253,9 @@ class ObjectManager
|
||||
$sUrl .= "&iPage=".$this->oMultiPage->iPage;
|
||||
}
|
||||
|
||||
if($this->oFilters)
|
||||
$sUrl .= $this->oFilters->getUrlData();
|
||||
|
||||
if($this->oSortInfo && $this->oSortInfo->sCurrentSort)
|
||||
{
|
||||
$sUrl .= "&sOrderBy={$this->oSortInfo->sCurrentSort}";
|
||||
@@ -1267,6 +1287,9 @@ class ObjectManager
|
||||
$this->oMultiPage->iPage."\">\n";
|
||||
}
|
||||
|
||||
if($this->oFilters)
|
||||
$sReturn .= $this->oFilters->getHiddenFormData();
|
||||
|
||||
if($this->sReturnToTitle)
|
||||
$sReturn .= "<input type=\"hidden\" name=\"sReturnToTitle\" value=\"".$this->sReturnToTitle."\">\n";
|
||||
|
||||
@@ -1310,6 +1333,20 @@ class ObjectManager
|
||||
echo $oTableRow->GetString();
|
||||
}
|
||||
|
||||
private function handleFilterControls($aClean)
|
||||
{
|
||||
/* Show filter info */
|
||||
if($this->oFilters)
|
||||
{
|
||||
echo "<form method=\"post\" action=\"".$this->makeUrl()."\" >";
|
||||
|
||||
echo $this->oFilters->getEditor();
|
||||
|
||||
echo "<br><input type='submit' value='Submit' name='sSubmit' >";
|
||||
echo "</form>";
|
||||
}
|
||||
}
|
||||
|
||||
private function handleMultiPageControls($aClean, $bItemsPerPageSelector = TRUE)
|
||||
{
|
||||
/* Display multi-page browsing controls (prev, next etc.) if applicable.
|
||||
@@ -1361,7 +1398,7 @@ class ObjectManager
|
||||
$sControls .= "</form>";
|
||||
}
|
||||
|
||||
$iTotalEntries = $oObject->objectGetEntriesCount($this->sState);
|
||||
$iTotalEntries = $oObject->objectGetEntriesCount($this->sState, $this->oFilters);
|
||||
$iNumPages = ceil($iTotalEntries / $iItemsPerPage);
|
||||
if($iNumPages == 0)
|
||||
$iNumPages = 1;
|
||||
|
||||
@@ -23,6 +23,7 @@ require_once(BASE.'include/application_queue.php');
|
||||
require_once(BASE.'include/version_queue.php');
|
||||
require_once(BASE.'include/testData_queue.php');
|
||||
require_once(BASE.'include/bugs.php');
|
||||
require_once(BASE.'include/db_filter_ui.php');
|
||||
|
||||
/* if we have no valid class name we should abort */
|
||||
if(!isset($aClean['sClass']))
|
||||
@@ -59,6 +60,7 @@ if(isset($aClean['sReturnTo']))
|
||||
|
||||
$oObject->getMultiPageDataFromInput($aClean);
|
||||
$oObject->setSortInfo($aClean);
|
||||
$oObject->getFilterInfoFromInput($aClean);
|
||||
|
||||
$sClass = $oObject->getClass();
|
||||
$oOtherObject = new $sClass($oObject->getId());
|
||||
|
||||
Reference in New Issue
Block a user