Skip to content
Snippets Groups Projects
Commit a8bf9286 authored by Peter Rotich's avatar Peter Rotich
Browse files

Add the concept of built-in lists

Generalize custom list interface in order to provide for facility to manage
system's built-in list like ticket statuses, priorities ...etc.
parent 4827655e
No related branches found
No related tags found
No related merge requests found
......@@ -18,6 +18,7 @@
**********************************************************************/
require_once(INCLUDE_DIR . 'class.orm.php');
require_once(INCLUDE_DIR . 'class.forms.php');
require_once(INCLUDE_DIR . 'class.list.php');
require_once(INCLUDE_DIR . 'class.filter.php');
require_once(INCLUDE_DIR . 'class.signal.php');
......@@ -900,248 +901,6 @@ class DynamicFormEntryAnswer extends VerySimpleModel {
}
}
/**
* Dynamic lists are used to represent list of arbitrary data that can be
* used as dropdown or typeahead selections in dynamic forms. This model
* defines a list. The individual items are stored in the DynamicListItem
* model.
*/
class DynamicList extends VerySimpleModel {
static $meta = array(
'table' => LIST_TABLE,
'ordering' => array('name'),
'pk' => array('id'),
);
var $_items;
var $_form;
function getSortModes() {
return array(
'Alpha' => 'Alphabetical',
'-Alpha' => 'Alphabetical (Reversed)',
'SortCol' => 'Manually Sorted'
);
}
function getListOrderBy() {
switch ($this->sort_mode) {
case 'Alpha': return 'value';
case '-Alpha': return '-value';
case 'SortCol': return 'sort';
}
}
function getPluralName() {
if ($name = $this->get('name_plural'))
return $name;
else
return $this->get('name') . 's';
}
function getAllItems() {
return DynamicListItem::objects()->filter(
array('list_id'=>$this->get('id')))
->order_by($this->getListOrderBy());
}
function getItems($limit=false, $offset=false) {
if (!$this->_items) {
$this->_items = DynamicListItem::objects()->filter(
array('list_id'=>$this->get('id'),
'status__hasbit'=>DynamicListItem::ENABLED))
->order_by($this->getListOrderBy());
if ($limit)
$this->_items->limit($limit);
if ($offset)
$this->_items->offset($offset);
}
return $this->_items;
}
function getItemCount() {
return DynamicListItem::objects()->filter(array('list_id'=>$this->id))
->count();
}
function getConfigurationForm() {
if (!$this->_form) {
$this->_form = DynamicForm::lookup(
array('type'=>'L'.$this->get('id')));
}
return $this->_form;
}
function getProperties() {
if ($f = $this->getForm())
return $f->getFields();
return array();
}
function getForm() {
return $this->getConfigurationForm();
}
function save($refetch=false) {
if (count($this->dirty))
$this->set('updated', new SqlFunction('NOW'));
if (isset($this->dirty['notes']))
$this->notes = Format::sanitize($this->notes);
return parent::save($refetch);
}
function delete() {
$fields = DynamicFormField::objects()->filter(array(
'type'=>'list-'.$this->id))->count();
if ($fields == 0)
return parent::delete();
else
// Refuse to delete lists that are in use by fields
return false;
}
static function create($ht=false) {
$inst = parent::create($ht);
$inst->set('created', new SqlFunction('NOW'));
return $inst;
}
static function getSelections() {
$selections = array();
foreach (DynamicList::objects() as $list) {
$selections['list-'.$list->id] =
array($list->getPluralName(),
SelectionField, $list->get('id'));
}
return $selections;
}
}
FormField::addFieldTypes('Custom Lists', array('DynamicList', 'getSelections'));
/**
* Represents a single item in a dynamic list
*
* Fields:
* value - (char * 255) Actual list item content
* extra - (char * 255) Other values that represent the same item in the
* list, such as an abbreviation. In practice, should be a
* space-separated list of tokens which should hit this list item in a
* search
* sort - (int) If sorting by this field, represents the numeric sort order
* that this item should come in the dropdown list
*/
class DynamicListItem extends VerySimpleModel {
static $meta = array(
'table' => LIST_ITEM_TABLE,
'pk' => array('id'),
'joins' => array(
'list' => array(
'null' => true,
'constraint' => array('list_id' => 'DynamicList.id'),
),
),
);
var $_config;
var $_form;
const ENABLED = 0x0001;
protected function hasStatus($flag) {
return 0 !== ($this->get('status') & $flag);
}
protected function clearStatus($flag) {
return $this->set('status', $this->get('status') & ~$flag);
}
protected function setStatus($flag) {
return $this->set('status', $this->get('status') | $flag);
}
function isEnabled() {
return $this->hasStatus(self::ENABLED);
}
function enable() {
$this->setStatus(self::ENABLED);
}
function disable() {
$this->clearStatus(self::ENABLED);
}
function getConfiguration() {
if (!$this->_config) {
$this->_config = $this->get('properties');
if (is_string($this->_config))
$this->_config = JsonDataParser::parse($this->_config);
elseif (!$this->_config)
$this->_config = array();
}
return $this->_config;
}
function getFilterData() {
$raw = $this->getConfiguration();
$props = array();
if ($form = $this->getConfigurationForm()) {
foreach ($form->getFields() as $field) {
$tag = $field->get('id');
if (isset($raw[$tag]))
$props[".$tag"] = $field->toString($raw[$tag]);
}
}
return $props;
}
function setConfiguration(&$errors=array()) {
$config = array();
foreach ($this->getConfigurationForm()->getFields() as $field) {
$val = $field->to_database($field->getClean());
$config[$field->get('id')] = is_array($val) ? $val[1] : $val;
$errors = array_merge($errors, $field->errors());
}
if (count($errors) === 0)
$this->set('properties', JsonDataEncoder::encode($config));
return count($errors) === 0;
}
function getConfigurationForm() {
if (!$this->_form) {
$this->_form = DynamicForm::lookup(
array('type'=>'L'.$this->get('list_id')));
}
return $this->_form;
}
function getVar($name) {
$config = $this->getConfiguration();
$name = mb_strtolower($name);
foreach ($this->getConfigurationForm()->getFields() as $field) {
if (mb_strtolower($field->get('name')) == $name)
return $config[$field->get('id')];
}
}
function toString() {
return $this->get('value');
}
function __toString() {
return $this->toString();
}
function delete() {
# Don't really delete, just unset the list_id to un-associate it with
# the list
$this->set('list_id', null);
return $this->save();
}
}
class SelectionField extends FormField {
static $widget = 'SelectionWidget';
......
<?php
/*********************************************************************
class.list.php
Custom List utils
Jared Hancock <jared@osticket.com>
Peter Rotich <peter@osticket.com>
Copyright (c) 2014 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
require_once(INCLUDE_DIR .'class.dynamic_forms.php');
/**
* Interface for Custom Lists
*
* Custom lists are used to represent list of arbitrary data that can be
* used as dropdown or typeahead selections in dynamic forms. This model
* defines a list. The individual items are stored in the "Item" model.
*
*/
interface CustomList {
function getId();
function getName();
function getPluralName();
function getNumItems();
function getAllItems();
function getItems($criteria);
function getForm(); // Config form
function hasProperties();
function getSortModes();
function getListOrderBy();
function isBuiltIn();
function update($vars, &$errors);
function delete();
static function create($vars, &$errors);
}
/*
* Base class for Built-in Custom Lists
*
* Built-in custom lists are lists that can be extended but within the
* constrains of system defined parameters.
*
*/
abstract class BuiltInCustomList implements CustomList {
static $sort_modes = array(
'Alpha' => 'Alphabetical',
'-Alpha' => 'Alphabetical (Reversed)',
'SortCol' => 'Manually Sorted'
);
var $config = null;
function __construct() {
$this->config = new Config('CL'.$this->getId());
}
abstract function getId();
abstract function getName();
abstract function getPluralName();
abstract function getInfo();
abstract function getNumItems();
abstract function getAllItems();
abstract function getItems($criteria);
abstract function getForm(); // Config form
abstract function hasProperties();
abstract function getListOrderBy();
function getSortModes() {
return static::$sort_modes;
}
function isBuiltIn() {
return true;
}
function set($field, $value) {
if (!$this->config)
return false;
return $this->config->set($field, $value);
}
abstract function update($vars, &$errors);
// Built-in list cannot be deleted
function delete() {
return false;
}
// Built-in list is defined - not created.
static function create($vars, &$errors) {
return false;
}
static function lookup($id) {
if (!($list=static::getLists())
// Built-in list exists
|| !isset($list[$id])
// Handler exits
|| !($handler = $list[$id]['handler'])
// It's a collable handler
|| !class_exists($handler))
return null;
return new $handler();
}
static function getLists() {
//TODO: define built-in lists
return array();
}
}
/**
* Dynamic lists are Custom Lists solely defined by the user.
*
*/
class DynamicList extends VerySimpleModel implements CustomList {
static $meta = array(
'table' => LIST_TABLE,
'ordering' => array('name'),
'pk' => array('id'),
);
static $sort_modes = array(
'Alpha' => 'Alphabetical',
'-Alpha' => 'Alphabetical (Reversed)',
'SortCol' => 'Manually Sorted'
);
// Required fields
static $fields = array('name', 'name_plural', 'sort_mode', 'notes');
var $_items;
var $_form;
function getId() {
return $this->get('id');
}
function isBuiltIn() {
return false;
}
function getInfo() {
return $this->ht;
}
function hasProperties() {
return ($this->getForm() && $this->getForm()->getFields());
}
function getSortModes() {
return static::$sort_modes;
}
function getListOrderBy() {
switch ($this->sort_mode) {
case 'Alpha': return 'value';
case '-Alpha': return '-value';
case 'SortCol': return 'sort';
}
}
function getName() {
return $this->get('name');
}
function getPluralName() {
if ($name = $this->get('name_plural'))
return $name;
else
return $this->get('name') . 's';
}
function getItemCount() {
return DynamicListItem::objects()->filter(array('list_id'=>$this->id))
->count();
}
function getNumItems() {
return $this->getItemCount();
}
function getAllItems() {
return DynamicListItem::objects()->filter(
array('list_id'=>$this->get('id')))
->order_by($this->getListOrderBy());
}
function getItems($limit=false, $offset=false) {
if (!$this->_items) {
$this->_items = DynamicListItem::objects()->filter(
array('list_id'=>$this->get('id'),
'status__hasbit'=>DynamicListItem::ENABLED))
->order_by($this->getListOrderBy());
if ($limit)
$this->_items->limit($limit);
if ($offset)
$this->_items->offset($offset);
}
return $this->_items;
}
function addItem($vars) {
$item = DynamicListItem::create(array(
'list_id' => $this->getId(),
'sort' => $vars['sort'],
'value' => $vars['value'],
'extra' => $vars['abbrev']
));
$item->save();
$this->_items = false;
return $item;
}
function getConfigurationForm() {
if (!$this->_form) {
$this->_form = DynamicForm::lookup(
array('type'=>'L'.$this->get('id')));
}
return $this->_form;
}
function getForm() {
return $this->getConfigurationForm();
}
function update($vars, &$errors) {
$required = array('name');
foreach (static::$fields as $f) {
if (in_array($f, $required) && !$vars[$f])
$errors[$f] = sprintf('%s is required', mb_convert_case($f, MB_CASE_TITLE));
elseif (isset($vars[$f]))
$this->set($f, $vars[$f]);
}
if ($errors)
return false;
return $this->save(true);
}
function save($refetch=false) {
if (count($this->dirty))
$this->set('updated', new SqlFunction('NOW'));
if (isset($this->dirty['notes']))
$this->notes = Format::sanitize($this->notes);
return parent::save($refetch);
}
function delete() {
$fields = DynamicFormField::objects()->filter(array(
'type'=>'list-'.$this->id))->count();
if ($fields == 0)
return parent::delete();
else
// Refuse to delete lists that are in use by fields
return false;
}
static function create($ht=false, &$errors=array()) {
$inst = parent::create($ht);
$inst->set('created', new SqlFunction('NOW'));
return $inst;
}
static function getSelections() {
$selections = array();
foreach (DynamicList::objects() as $list) {
$selections['list-'.$list->id] =
array($list->getPluralName(),
SelectionField, $list->get('id'));
}
return $selections;
}
}
FormField::addFieldTypes('Custom Lists', array('DynamicList', 'getSelections'));
/**
* Represents a single item in a dynamic list
*
* Fields:
* value - (char * 255) Actual list item content
* extra - (char * 255) Other values that represent the same item in the
* list, such as an abbreviation. In practice, should be a
* space-separated list of tokens which should hit this list item in a
* search
* sort - (int) If sorting by this field, represents the numeric sort order
* that this item should come in the dropdown list
*/
class DynamicListItem extends VerySimpleModel {
static $meta = array(
'table' => LIST_ITEM_TABLE,
'pk' => array('id'),
'joins' => array(
'list' => array(
'null' => true,
'constraint' => array('list_id' => 'DynamicList.id'),
),
),
);
var $_config;
var $_form;
const ENABLED = 0x0001;
protected function hasStatus($flag) {
return 0 !== ($this->get('status') & $flag);
}
protected function clearStatus($flag) {
return $this->set('status', $this->get('status') & ~$flag);
}
protected function setStatus($flag) {
return $this->set('status', $this->get('status') | $flag);
}
function isEnabled() {
return $this->hasStatus(self::ENABLED);
}
function enable() {
$this->setStatus(self::ENABLED);
}
function disable() {
$this->clearStatus(self::ENABLED);
}
function getId() {
return $this->get('id');
}
function getValue() {
return $this->get('value');
}
function getAbbrev() {
return $this->get('extra');
}
function getSortOrder() {
return $this->get('sort');
}
function getConfiguration() {
if (!$this->_config) {
$this->_config = $this->get('properties');
if (is_string($this->_config))
$this->_config = JsonDataParser::parse($this->_config);
elseif (!$this->_config)
$this->_config = array();
}
return $this->_config;
}
function setConfiguration(&$errors=array()) {
$config = array();
foreach ($this->getConfigurationForm()->getFields() as $field) {
$val = $field->to_database($field->getClean());
$config[$field->get('id')] = is_array($val) ? $val[1] : $val;
$errors = array_merge($errors, $field->errors());
}
if (count($errors) === 0)
$this->set('properties', JsonDataEncoder::encode($config));
return count($errors) === 0;
}
function getConfigurationForm() {
if (!$this->_form) {
$this->_form = DynamicForm::lookup(
array('type'=>'L'.$this->get('list_id')));
}
return $this->_form;
}
function getVar($name) {
$config = $this->getConfiguration();
$name = mb_strtolower($name);
foreach ($this->getConfigurationForm()->getFields() as $field) {
if (mb_strtolower($field->get('name')) == $name)
return $config[$field->get('id')];
}
}
function toString() {
return $this->get('value');
}
function __toString() {
return $this->toString();
}
function update($vars, $save = true) {
foreach (array(
'sort' => 'sort',
'value' => 'value',
'abbrev' => 'extra') as $k => $v) {
if (isset($vars[$k]))
$this->set($v, $vars[$k]);
}
if ($save)
$this->save();
return true;
}
function delete() {
# Don't really delete, just unset the list_id to un-associate it with
# the list
$this->set('list_id', null);
return $this->save();
}
}
?>
<?php
$info=array();
if($list && !$errors) {
$title = 'Update custom list';
if ($list) {
$title = 'Update list';
$action = 'update';
$submit_text='Save Changes';
$info = $list->ht;
$info = $list->getInfo();
$newcount=2;
} else {
$title = 'Add new custom list';
......@@ -13,7 +13,8 @@ if($list && !$errors) {
$submit_text='Add List';
$newcount=4;
}
$info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
$info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info);
?>
<form action="?" method="post" id="save">
......@@ -21,7 +22,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
<input type="hidden" name="do" value="<?php echo $action; ?>">
<input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>">
<input type="hidden" name="id" value="<?php echo $info['id']; ?>">
<h2>Custom List</h2>
<h2>Custom List: <?php echo $list->getName(); ?></h2>
<ul class="tabs">
<li><a href="#definition" class="active">
......@@ -45,17 +46,36 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
<tbody>
<tr>
<td width="180" class="required">Name:</td>
<td><input size="50" type="text" name="name" value="<?php echo $info['name']; ?>"/>
<span class="error">*<br/><?php echo $errors['name']; ?></td>
<td>
<?php
if ($list->isBuiltIn())
echo $info['name'];
else {
echo sprintf('<input size="50" type="text" name="name"
value="%s"/> <span
class="error">*<br/>%s</span>',
$info['name'], $errors['name']);
}
?>
</td>
</tr>
<tr>
<td width="180">Plural Name:</td>
<td><input size="50" type="text" name="name_plural" value="<?php echo $info['name_plural']; ?>"/></td>
<td>
<?php
if ($list->isBuiltIn())
echo $info['name_plural'];
else
echo sprintf('<input size="50" type="text"
name="name_plural" value="%s"/>',
$info['name_plural']);
?>
</td>
</tr>
<tr>
<td width="180">Sort Order:</td>
<td><select name="sort_mode">
<?php foreach (DynamicList::getSortModes() as $key=>$desc) { ?>
<?php foreach ($list->getSortModes() as $key=>$desc) { ?>
<option value="<?php echo $key; ?>" <?php
if ($key == $info['sort_mode']) echo 'selected="selected"';
?>><?php echo $desc; ?></option>
......@@ -184,9 +204,9 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
<thead>
<?php if ($list) {
$page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1;
$count = $list->getItemCount();
$count = $list->getNumItems();
$pageNav = new Pagenate($count, $page, PAGE_LIMIT);
$pageNav->setURL('dynamic-list.php', 'id='.urlencode($_REQUEST['id']));
$pageNav->setURL('list.php', 'id='.urlencode($list->getId()));
$showing=$pageNav->showing().' list items';
?>
<?php }
......@@ -200,7 +220,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
<tr>
<th></th>
<th>Value</th>
<th>Extra <em style="display:inline">&mdash; abbreviations and such</em></th>
<th>Abbrev <em style="display:inline">&mdash; Abbreviations and such</em></th>
<th>Disabled</th>
<th>Delete</th>
</tr>
......@@ -208,22 +228,22 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
<tbody <?php if ($info['sort_mode'] == 'SortCol') { ?>
class="sortable-rows" data-sort="sort-"<?php } ?>>
<?php if ($list)
$icon = ($info['sort_mode'] == 'SortCol')
? '<i class="icon-sort"></i>&nbsp;' : '';
<?php
if ($list) {
$icon = ($info['sort_mode'] == 'SortCol')
? '<i class="icon-sort"></i>&nbsp;' : '';
foreach ($list->getAllItems() as $i) {
$id = $i->get('id'); ?>
$id = $i->getId(); ?>
<tr class="<?php if (!$i->isEnabled()) echo 'disabled'; ?>">
<td><?php echo $icon; ?>
<input type="hidden" name="sort-<?php echo $id; ?>"
value="<?php echo $i->get('sort'); ?>"/></td>
value="<?php echo $i->getSortOrder(); ?>"/></td>
<td><input type="text" size="40" name="value-<?php echo $id; ?>"
value="<?php echo $i->get('value'); ?>"/>
<?php if ($form && $form->getFields()) { ?>
value="<?php echo $i->getValue(); ?>"/>
<?php if ($list->hasProperties()) { ?>
<a class="action-button" style="float:none;overflow:inherit"
href="#ajax.php/list/item/<?php
echo $i->get('id'); ?>/properties"
echo $id ?>/properties"
onclick="javascript:
$('#overlay').show();
$('#field-config .body').load($(this).attr('href').substr(1));
......@@ -231,8 +251,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
return false;
"><i class="icon-edit"></i> Properties</a>
<?php } ?></td>
<td><input type="text" size="30" name="extra-<?php echo $id; ?>"
value="<?php echo $i->get('extra'); ?>"/></td>
<td><input type="text" size="30" name="abbrev-<?php echo $id; ?>"
value="<?php echo $i->getAbbrev(); ?>"/></td>
<td>
<input type="checkbox" name="disable-<?php echo $id; ?>" <?php
if (!$i->isEnabled()) echo 'checked="checked"'; ?>/></td>
......@@ -246,7 +266,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
<td><?php echo $icon; ?> <em>+</em>
<input type="hidden" name="sort-new-<?php echo $i; ?>"/></td>
<td><input type="text" size="40" name="value-new-<?php echo $i; ?>"/></td>
<td><input type="text" size="30" name="extra-new-<?php echo $i; ?>"/></td>
<td><input type="text" size="30" name="abbrev-new-<?php echo $i; ?>"/></td>
<td></td>
<td></td>
</tr>
......
<?php
require('admin.inc.php');
require_once(INCLUDE_DIR."/class.dynamic_forms.php");
require_once(INCLUDE_DIR.'class.list.php');
$list=null;
if($_REQUEST['id'] && !($list=DynamicList::lookup($_REQUEST['id'])))
......@@ -14,42 +15,42 @@ if($_POST) {
$required = array('name');
switch(strtolower($_POST['do'])) {
case 'update':
foreach ($fields as $f)
if (in_array($f, $required) && !$_POST[$f])
$errors[$f] = sprintf('%s is required',
mb_convert_case($f, MB_CASE_TITLE));
elseif (isset($_POST[$f]))
$list->set($f, $_POST[$f]);
if ($errors)
$errors['err'] = 'Unable to update custom list. Correct any error(s) below and try again.';
elseif ($list->save(true))
if ($list->update($_POST, $errors))
$msg = 'Custom list updated successfully';
elseif ($errors)
$errors['err'] = 'Unable to update custom list. Correct any error(s) below and try again.';
else
$errors['err'] = 'Unable to update custom list. Unknown internal error';
foreach ($list->getAllItems() as $item) {
$id = $item->get('id');
if ($_POST["delete-$id"] == 'on') {
$item->delete();
continue;
}
foreach (array('sort','value','extra') as $i)
if (isset($_POST["$i-$id"]))
$item->set($i, $_POST["$i-$id"]);
if ($list->getNumItems()) {
foreach ($list->getAllItems() as $item) {
$id = $item->getId();
if ($_POST["delete-$id"] == 'on') {
$item->delete();
continue;
}
if ($_POST["disable-$id"] == 'on')
$item->disable();
else
$item->enable();
$item->update(array(
'value' => $_POST["value-$id"],
'abbrev' => $_POST["abbrev-$id"],
'sort' => $_POST["name-$id"],
),
false);
if ($_POST["disable-$id"] == 'on')
$item->disable();
else
$item->enable();
$item->save();
$item->save();
}
}
$names = array();
if (!$form) {
$form = DynamicForm::create(array(
'type'=>'L'.$_REQUEST['id'],
'title'=>$_POST['name'] . ' Properties'
'type' => 'L'.$list->getId(),
'title' => $list->getName() . ' Properties'
));
$form->save(true);
}
......@@ -138,19 +139,16 @@ if($_POST) {
}
if ($list) {
for ($i=0; isset($_POST["prop-sort-new-$i"]); $i++) {
for ($i=0; isset($_POST["sort-new-$i"]); $i++) {
if (!$_POST["value-new-$i"])
continue;
$item = DynamicListItem::create(array(
'list_id'=>$list->get('id'),
'sort'=>$_POST["sort-new-$i"],
'value'=>$_POST["value-new-$i"],
'extra'=>$_POST["extra-new-$i"]
));
$item->save();
$list->addItem(array(
'value' => $_POST["value-new-$i"],
'abbrev' =>$_POST["abbrev-new-$i"],
'sort' => $_POST["sort-new-$i"]
));
}
# Invalidate items cache
$list->_items = false;
}
if ($form) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment