'item', //singular name of the listed records
'plural' => 'items', //plural name of the listed records
'ajax' => false //does this table support ajax?
);
protected $BulkActions= array(
//'Edit' => '__Edit',
'Delete' => '__Delete',
//'change_cat' => 'Change Category',
//'set_off' => 'Set Offline',
//'set_on' => 'Set Online',
);
protected $Columns= array(
'Headers'=> array(
'cb' => '', //Render a checkbox instead of text
'name' => '__Name'/* def */,
'size' => '__Size'/* def */,
'desc' => '__Description'/* def */,
'date' => '__Date'/* def */,
),
'Sortable'=> array(
'name' => array('file_display_name', false),
'size' => array('file_size', false),
'desc' => array('file_description', false),
'date' => array('file_date', false),
),
'Hidden'=> array(
),
);
// internal properites
protected $Id= 0; // value of 'id' field of currently focused record
protected $ItemData; // loaded record
protected $UploadsDir= ''; // path to directory of images
protected $UploadsURL= ''; // URL to directory of images
protected $Action; // current action
protected $SearchJoins= ''; // additional sql joins for filtering list
/**
* Constructor.
*/
public function __construct() {
global $wpdb;
$this->Table= $wpdb->prefix.$this->Table;
$UD= wp_get_upload_dir(); // reassign UploadsDir because "uploads" can be reallocated
$this->UploadsDir= $UD['basedir'].'/mfloormap';
$this->UploadsURL= $UD['baseurl'].'/mfloormap';
// parent
parent::__construct($this->ConstructorOptions);
// translate definitions
$this->BulkActions= $this->TranslateDefinitions($this->BulkActions);
$this->Columns['Headers']= $this->TranslateDefinitions($this->Columns['Headers']);
}
/**
* Execute specific action on this manager.
*/
public function Run() {
$Act= $this->Input('action', 'T');
$this->Action= $Act;
$Method= method_exists($this, 'Action_'.$Act)
? 'Action_'.$Act
: 'Action_List';
$this->$Method();
}
/**
* Helper utility for fetching values from request (POST/GET).
* Sanitizer is string consist of following letters:
* - "A": result must be array, other modifiers will be applied for each element of array
* - "I": result must be integer
* - "T": function "trim" will be applied on result string
*
* @param string $Key
* @param string $Sanitize
* @param mixed $Default
* @return mixed
*/
protected function Input($Key, $Sanitize='I', $Default=null) {
if (isset($_POST[$Key])) {
return $this->InputSanitizer($_POST[$Key], $Sanitize);
}
if (isset($_GET[$Key])) {
return $this->InputSanitizer($_GET[$Key], $Sanitize);
}
return $Default !== null
? $Default
: (strpos($Sanitize, 'A') !== false ? array()
: (strpos($Sanitize, 'I') !== false ? 0 : ''));
}
/**
* Helper method for Input().
* @param mixed $Value
* @param string $Sanitize
* @return mixed
*/
protected function InputSanitizer($Value, $Sanitize) {
if (strpos($Sanitize, 'A') !== false) {
// if value is array call this method recursive for each element
if (!is_array($Value)) {
return array(); // serious logical error
}
array_walk($Value, array($this,'InputModifier'), str_replace('A','',$Sanitize));
return $Value;
}
if (strpos($Sanitize, 'I') !== false) { // convert to integer
$Value= intval($Value);
}
if (strpos($Sanitize, 'T') !== false) { // trim string
$Value= trim($Value);
}
if (is_string($Value)) { // remove wp's "magic" quotes
$Value= stripslashes($Value);
}
return $Value;
}
/**
* List of available bulk actions in table.
* Overriding parent.
*/
protected function get_bulk_actions() {
return $this->BulkActions;
}
/**
* Getters of columns definitions.
* Overriding parent.
*/
public function get_columns() {
return $this->Columns['Headers'];
}
protected function get_sortable_columns() {
return $this->Columns['Sortable'];
}
protected function get_hidden_columns() {
return $this->Columns['Hidden'];
}
/**
* Gets the name of the default primary column.
* Overriding parent.
*/
protected function get_default_primary_column_name() {
return $this->PrimaryKey;
}
/**
* Executes bulk actions.
*/
protected function HandleBulkActions() {
$Items= $this->Input('items', 'AI');
if (!$this->current_action() || !$this->Input('action2') || empty($Items)) {
return;
}
$Count= 0;
switch ($this->current_action()) {
case 'Delete':
foreach ($Items as $Id) {
if (!$this->CanDelete($Id)) {
continue;
}
$Count++;
$this->DeleteItem($Id);
}
if ($Count > 0) {
$Message= sprintf(esc_html__('ConfirmNItemsDeleted', $this->TextDomain), $Count);
$this->NotificationConfirmation($Message);
}
break;
}
}
/**
* Check wether item can be deleted.
*
* @param mixed $Id
* @return boolean
*/
protected function CanDelete($Id) {
return true;
}
/**
* Perform deleting specified item.
* Called by bulk operatations handler.
*/
protected function DeleteItem($Id) {
// delete record from database
global $wpdb;
$Result= $wpdb->delete($this->Table, array($this->PrimaryKey => $Id), array('%d'));
// notify about errors
if (isset($Result['error']) && $Result['error']) {
$this->NotificationError($Result['error'].'
'. __("Back").'');
}
}
/**
* Handle reordering items in tables woth "Ordering" column.
*
* @param integer $Id database id of target item
* @param string $Direction either 'Up' or 'Dn'
*/
protected function ReorderItem($Id, $Direction) {
global $wpdb;
// get list of orders
$Where= $this->GetSearchWhere();
$Order= $this->GetSearchOrder();
$SQL= "SELECT T1.$this->PrimaryKey, T1.Ordering FROM $this->Table AS T1 $this->SearchJoins $Where ORDER BY $Order";
$Items= $wpdb->get_results($SQL, ARRAY_A);
// find target item
$TargetKey= -1;
foreach($Items as $Key=>$Item) {
if ($Item[$this->PrimaryKey] === (string)$Id) {
$TargetKey= $Key;
break;
}
}
$TargetKey2= $Direction === 'Up'
? $TargetKey-1
: $TargetKey+1;
if ($TargetKey === -1 || !isset($Items[$TargetKey2])) {
return;
}
// swap ordering values
$SQL= "UPDATE $this->Table SET Ordering=%d WHERE $this->PrimaryKey=%d";
$Result= $wpdb->query($wpdb->prepare($SQL, $Items[$TargetKey2]['Ordering'], $Id));
if (isset($Result['error']) && $Result['error']) {
$this->NotificationError($Result['error']);
return;
}
$Result= $wpdb->query($wpdb->prepare($SQL, $Items[$TargetKey]['Ordering'], $Items[$TargetKey2][$this->PrimaryKey]));
if (isset($Result['error']) && $Result['error']) {
$this->NotificationError($Result['error']);
return;
}
}
/**
* Handle image uploading.
*/
protected function UploadImage($UploadKey, $DatabaseKey, $SubDir, $ItemId, $AddSuffix='') {
$Exts= array('gif','png','jpg','jpe','jpeg');
if (!isset($_FILES[$UploadKey])) {
return;
}
// is there upload attempt
if ($_FILES[$UploadKey]['tmp_name'] === 'none' || $_FILES[$UploadKey]['tmp_name'] === ''
|| $_FILES[$UploadKey]['size'] === 0 || !is_uploaded_file($_FILES[$UploadKey]['tmp_name'])) {
return;
}
// get exiting image
global $wpdb;
$Results= $wpdb->get_results("SELECT $DatabaseKey FROM $this->Table WHERE $this->PrimaryKey=$ItemId LIMIT 1", ARRAY_A);
$ExistingImage= $Results[0][$DatabaseKey];
// validate uploading image
$NameParts= explode('.', basename($_FILES[$UploadKey]['name']));
$Ext= strtolower(array_pop($NameParts));
if (!in_array($Ext, $Exts)) {
$this->NotificationError(esc_html(sprintf(__('Error.ImageUpload.Ext','mFloorMap'),implode(',',$Exts))));
return;
}
$BaseName= sanitize_file_name(implode('.', $NameParts));
$BaseName= preg_replace("/[^(\x20-\x7F)]*/", "", $BaseName ); // remove non-ascii letters
if (strlen($BaseName) === 0) {
$this->NotificationError(esc_html(sprintf(__('Error.ImageUpload.Name','mFloorMap'),$BaseName)));
return;
}
// ensure existance of file directory
if (!is_dir($this->UploadsDir.'/'.$SubDir)) {
mkdir($this->UploadsDir.'/'.$SubDir, 0775, true);
chmod($this->UploadsDir.'/'.$SubDir, 0775);
}
// delete existing file
if ($ExistingImage <> '' && is_file("$this->UploadsDir/$SubDir/$ExistingImage")) {
unlink("$this->UploadsDir/$SubDir/$ExistingImage");
}
// new filename contains ID to easily identify for which database entity this image is used,
// and timestamp to mitigate browser's caching after uploading newer image
$NewName= $ItemId.'-'.$BaseName.$AddSuffix.'-'.time().'.'.$Ext;
// move uploaded image to target directory
if (!move_uploaded_file($_FILES[$UploadKey]['tmp_name'], "$this->UploadsDir/$SubDir/$NewName")) {
$this->NotificationError(esc_html__('Error.ImageUpload.Write','mFloorMap').": $this->UploadsDir/$SubDir/$NewName");
return;
}
// update database
$SQL= "UPDATE $this->Table SET $DatabaseKey='%s' WHERE $this->PrimaryKey=".intval($ItemId);
$Result= $wpdb->query($wpdb->prepare($SQL, $NewName));
if (isset($Result['error']) && $Result['error']) {
$this->NotificationError($Result['error']);
}
}
//------------------------------------------------------------------------
// Column renderers
// -----------------------------------------------------------------------
/*
* Renderers of cell content.
*/
protected function column_default($Item, $ColumnName) {
//if (strpos($column_name, 'file_') !== 0) $column_name = "file_" . $column_name;
return $Item[$ColumnName];
}
public function column_cb($Item) {
return sprintf('', $Item['Id']);
}
protected function column_title($Item) {
$URL= admin_url("admin.php?page={$this->MenuSlug}&action=View&id=$Item[Id]"
.(defined('DOING_AJAX')?"&redirect_referer=1":""));
$Title= esc_attr(sprintf(__('Edit “%s”'), $Item['Title']));
$Caption= ($Item['Title']) ? esc_html($Item['Title']) : '?'; // at least "?" need to be clickable
return ''.$Caption.'';
}
protected function column_published($Item) {
return $Class= $Item['Published'] === '1'
? '✓'
: '✗'; // ☓ ✖
}
protected function column_shortcode($Item) {
return '['.$this->ShortCode.' id="'.$Item[$this->PrimaryKey].'"]';
}
protected function column_ordering($Item) {
$Column = $this->Input('orderby', 'T');
$Direction= $this->Input('order', 'T');
return $Column === 'Ordering' && $Direction === "asc"
? '
▲
▼'
: '-';
}
//------------------------------------------------------------------------
// Actions
// -----------------------------------------------------------------------
/**
* Action: create new item.
*/
protected function Action_New() {
$this->Id= 0;
$this->DisplayForm();
}
/**
* Action: show details of selected item and allow editing it.
*/
protected function Action_View() {
$Id= $this->Input('id', 'I');
if ($Id) {
$this->Id= $Id;
$this->DisplayForm();
} else {
echo 'Invalid id.';
}
}
/**
* Default action: display list of items.
*/
protected function Action_List() {
// execute bulk operations
$this->HandleBulkActions();
$BtnAddURL= admin_url("admin.php?page=$this->MenuSlug&action=New");
$BtnAddText= __('Btn.ListAdd', $this->TextDomain);
$BtnAdd= ''.$BtnAddText.'';
$H2Text= __($this->TDPrefix.'List.Caption', $this->TextDomain);
$Nonce='';//wp_nonce_field(__FILE__, $this->MenuSlug.'-nonce', false, false);
?>
'.esc_html($Message).'
'.esc_html($Message).'