Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?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 getItem($id);
function addItem($vars, &$errors);
function getForm(); // Config form
function hasProperties();
function getSortModes();
function allowAdd();
function hasAbbrev();
function update($vars, &$errors);
function delete();
static function create($vars, &$errors);
static function lookup($id);
}
/*
* Custom list item interface
*/
interface CustomListItem {
function getId();
function getValue();
function getAbbrev();
function getSortOrder();
function getConfiguration();
function getConfigurationForm();
function isEnabled();
function isDeletable();
function isEnableable();
function isInternal();
function enable();
function disable();
function update($vars, &$errors);
function delete();
* Base class for Custom List handlers
* Custom list handler extends custom list and might store data outside the
* typical dynamic list store.
abstract class CustomListHandler {
var $_list;
function __construct($list) {
$this->_list = $list;
function __call($name, $args) {
$rv = null;
if ($this->_list && is_callable(array($this->_list, $name)))
$rv = $args
? call_user_func_array(array($this->_list, $name), $args)
: call_user_func(array($this->_list, $name));
return $rv;
function update($vars, &$errors) {
return $this->_list->update($vars, $errors);
abstract function getNumItems();
abstract function getAllItems();
abstract function getItems($criteria);
abstract function getItem($id);
abstract function addItem($vars, &$errors);
}
/**
* 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');
// Supported masks
const MASK_EDIT = 0x0001;
const MASK_ADD = 0x0002;
const MASK_DELETE = 0x0004;
const MASK_ABBREV = 0x0008;
var $_config;
function __construct() {
call_user_func_array(array('parent', '__construct'), func_get_args());
$this->_config = new Config('list.'.$this->getId());
function getId() {
return $this->get('id');
}
function getInfo() {
return $this->ht;
}
function hasProperties() {
return ($this->getForm() && $this->getForm()->getFields());
}
function getSortModes() {
return static::$sort_modes;
}
function getSortMode() {
return $this->sort_mode;
}
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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 getItem($val) {
$criteria = array('list_id' => $this->getId());
if (is_int($val))
$criteria['id'] = $val;
else
$criteria['value'] = $val;
return DynamicListItem::lookup($criteria);
}
function addItem($vars, &$errors) {
$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($autocreate=false) {
$this->_form = DynamicForm::lookup(array('type'=>'L'.$this->getId()));
if (!$this->_form
&& $autocreate
&& $this->createConfigurationForm())
return $this->getConfigurationForm(false);
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
function isDeleteable() {
return !$this->hasMask(static::MASK_DELETE);
}
function isEditable() {
return !$this->hasMask(static::MASK_EDIT);
}
function allowAdd() {
return !$this->hasMask(static::MASK_ADD);
}
function hasAbbrev() {
return !$this->hasMask(static::MASK_ABBREV);
}
protected function hasMask($mask) {
return 0 !== ($this->get('masks') & $mask);
}
protected function clearMask($mask) {
return $this->set('masks', $this->get('masks') & ~$mask);
}
protected function setFlag($mask) {
return $this->set('mask', $this->get('mask') | $mask);
}
private function createConfigurationForm() {
$form = DynamicForm::create(array(
'type' => 'L'.$this->getId(),
'title' => $this->getName() . ' Properties'
));
return $form->save(true);
}
function getForm($autocreate=true) {
return $this->getConfigurationForm($autocreate);
function getConfiguration() {
return JsonDataParser::parse($this->_config->get('configuration'));
}
$required = array();
if ($this->isEditable())
$required = array('name');
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
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;
}
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
private function createForm() {
$form = DynamicForm::create(array(
'type' => 'L'.$this->getId(),
'title' => $this->getName() . ' Properties'
));
return $form->save(true);
}
static function add($vars, &$errors) {
$required = array('name');
$ht = array();
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]))
$ht[$f] = $vars[$f];
}
if (!$ht || $errors)
return false;
// Create the list && form
if (!($list = self::create($ht))
|| !$list->save(true)
|| !$list->createConfigurationForm())
return false;
return $list;
}
static function create($ht=false, &$errors=array()) {
$inst = parent::create($ht);
$inst->set('created', new SqlFunction('NOW'));
if (isset($ht['properties'])) {
$inst->save();
$ht['properties']['type'] = 'L'.$inst->getId();
$form = DynamicForm::create($ht['properties']);
$form->save();
}
if (isset($ht['configuration'])) {
$inst->save();
$c = new Config('list.'.$inst->getId());
$c->set('configuration', JsonDataEncoder::encode($ht['configuration']));
}
if (isset($ht['items'])) {
$inst->save();
foreach ($ht['items'] as $i) {
$i['list_id'] = $inst->getId();
$item = DynamicListItem::create($i);
$item->save();
}
}
static function lookup($id) {
if (!($list = parent::lookup($id)))
return null;
if (($config = $list->getConfiguration())) {
if (($lh=$config['handler']) && class_exists($lh))
$list = new $lh($list);
}
return $list;
}
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 implements CustomListItem {
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;
const INTERNAL = 0x0002;
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);
}
return $this->hasStatus(self::INTERNAL);
}
function isEnableable() {
return true;
}
function isDeletable() {
return !$this->isInternal();
}
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 getListId() {
return $this->get('list_id');
}
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
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 getForm() {
return $this->getConfigurationForm();
}
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, &$errors=array()) {
if (!$vars['value']) {
$errors['value-'.$this->getId()] = 'Value required';
return false;
}
foreach (array(
'sort' => 'sort',
'value' => 'value',
'abbrev' => 'extra') as $k => $v) {
if (isset($vars[$k]))
$this->set($v, $vars[$k]);
}
}
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();
}
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
static function create($ht=false, &$errors=array()) {
if (isset($ht['properties']) && is_array($ht['properties']))
$ht['properties'] = JsonDataEncoder::encode($ht['properties']);
$inst = parent::create($ht);
$inst->save(true);
// Auto-config properties if any
if ($ht['configuration'] && is_array($ht['configuration'])) {
$config = $inst->getConfiguration();
if (($form = $inst->getConfigurationForm())) {
foreach ($form->getFields() as $f) {
if (!isset($ht['configuration'][$f->get('name')]))
continue;
if (is_array($ht['configuration'][$f->get('name')]))
$val = JsonDataEncoder::encode(
$ht['configuration'][$f->get('name')]);
else
$val = $ht['configuration'][$f->get('name')];
$config[$f->get('id')] = $val;
}
}
$inst->set('properties', JsonDataEncoder::encode($config));
}
return $inst;
}
class TicketStatusList extends CustomListHandler {
// Fields of interest we need to store
static $config_fields = array('sort_mode', 'notes');
return TicketStatus::objects()->order_by($this->getListOrderBy());
if (!$this->_items) {
$this->_items = TicketStatus::objects()->filter(
array('flags__hasbit' => TicketStatus::ENABLED))
->order_by($this->getListOrderBy());
if ($criteria['limit'])
$this->_items->limit($criteria['limit']);
if ($criteria['offset'])
$this->_items->offset($criteria['offset']);
}
return $this->_items;
function getItem($val) {
if (!is_int($val))
$val = array('name' => $val);
return TicketStatus::lookup($val, $this);
'flags' => 0,
'sort' => $vars['sort'],
'name' => $vars['value'],
));
$item->save();
$this->_items = false;
return $item;
}
static function __load() {
require_once(INCLUDE_DIR.'class.i18n.php');
$i18n = new Internationalization();
if ($f['type'] == 'ticket-status') {
$list = DynamicList::create($f);
$list->save();
if (!$list || !($o=DynamicForm::objects()->filter(
array('type'=>'L'.$list->getId()))))
return false;
// Create default statuses
if (($statuses = $i18n->getTemplate('ticket_status.yaml')->getData()))
foreach ($statuses as $status)
TicketStatus::__create($status);
return $o[0];
}
class TicketStatus extends VerySimpleModel implements CustomListItem {
static $meta = array(
'table' => TICKET_STATUS_TABLE,
'ordering' => array('name'),
'pk' => array('id'),
);
var $_list;
var $_form;
var $_config;
var $_settings;
const ENABLED = 0x0001;
const INTERNAL = 0x0002; // Forbid deletion or name and status change.
function __construct() {
call_user_func_array(array('parent', '__construct'), func_get_args());
$this->_config = new Config('TS.'.$this->getId());
}
protected function hasFlag($field, $flag) {
return 0 !== ($this->get($field) & $flag);
}
protected function clearFlag($field, $flag) {
return $this->set($field, $this->get($field) & ~$flag);
}
protected function setFlag($field, $flag) {
return $this->set($field, $this->get($field) | $flag);
}
protected function hasProperties() {
return ($this->_config->get('properties'));
}
function getForm() {
return $this->getConfigurationForm();
}
function getConfigurationForm() {
if (!$this->_form && $this->_list) {
$this->_form = DynamicForm::lookup(
array('type'=>'L'.$this->_list->getId()));
}
return $this->_form;
}
function isEnabled() {
return $this->hasFlag('mode', self::ENABLED);
}
function enable() {
// Ticket status without properties cannot be enabled!
if (!$this->isEnableable())
return false;
return $this->setFlag('mode', self::ENABLED);
}
function disable() {
return (!$this->isInternal()
&& $this->clearFlag('mode', self::ENABLED));
}
function isEnableable() {
return $this->hasProperties();
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
}
function isDeletable() {
return !$this->isInternal();
}
function isInternal() {
return ($this->hasFlag('mode', self::INTERNAL));
}
function getId() {
return $this->get('id');
}
function getName() {
return $this->get('name');
}
function getValue() {
return $this->getName();
}
function getAbbrev() {
return '';
}
function getSortOrder() {
return $this->get('sort');
}
function getConfiguration() {
if (!$this->_settings) {
$this->_settings = $this->_config->get('properties');
if (is_string($this->_settings))
$this->_settings = JsonDataParser::parse($this->_settings);
elseif (!$this->_settings)
$this->_settings = array();
if ($this->getConfigurationForm()) {
foreach ($this->getConfigurationForm()->getFields() as $f) {
$name = mb_strtolower($f->get('name'));
$id = $f->get('id');
switch($name) {
case 'flags':
foreach (TicketFlagField::$_flags as $k => $v)
$this->_settings[$id][$k] = $v['name'];
$this->_settings[$id][$this->get('state')] = $this->get('state');
break;
default:
if (!$this->_settings[$id] && $this->_settings[$name])
$this->_settings[$id] = $this->_settings[$name];
}
}
}
}
return $this->_settings;
}
function setConfiguration(&$errors=array()) {
$properties = array();
foreach ($this->getConfigurationForm()->getFields() as $f) {
if ($this->isInternal() //Item is internal.
&& !$f->isEditable())
continue;
$errors = array_merge($errors, $f->errors());
if ($f->errors()) continue;
$name = mb_strtolower($f->get('name'));
switch ($name) {
case 'flags':
if ($val && is_array($val)) {
$flags = 0;
foreach ($val as $k => $v) {
if (isset(TicketFlagField::$_flags[$k]))
$flags += TicketFlagField::$_flags[$k]['flag'];
elseif (!$f->errors())
$f->addError('Unknown or invalid flag', $name);
}
$this->set('flags', $flags);
} elseif ($val && !$f->errors()) {
$f->addError('Unknown or invalid flag format', $name);
}
break;
case 'state':
if ($val && is_array($val))
$this->set('state', key($val));
else
$f->addError('Unknown or invalid state', $name);
break;
default: //Custom properties the user might add.
// Add field specific validation errors (warnings)
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
$errors = array_merge($errors, $f->errors());
}
if (count($errors) === 0) {
$this->save(true);
$this->setProperties($properties);
}
return count($errors) === 0;
}
function setProperties($properties) {
if ($properties && is_array($properties))
$properties = JsonDataEncoder::encode($properties);
$this->_config->set('properties', $properties);
}
function update($vars, &$errors) {
$fields = array('value' => 'name', 'sort' => 'sort');
foreach($fields as $k => $v) {
if (isset($vars[$k]))
$this->set($v, $vars[$k]);
}
return $this->save(true);
}
function delete() {
if (!$this->isDeletable())
return false;
// TODO: Delete and do house cleaning (move tickets..etc)
}
function toString() {
return $this->getValue();
}
function __toString() {
return $this->toString();
}
static function lookup($var, $list= false) {
if (!($item = parent::lookup($var)))
return null;
$item->_list = $list;
return $item;
}
static function __create($ht, &$error=false) {
global $ost;
$properties = JsonDataEncoder::encode($ht['properties']);
unset($ht['properties']);
$ht['created'] = new SqlFunction('NOW');
if ($status = TicketStatus::create($ht)) {
$status->save(true);
$status->_config = new Config('TS.'.$status->getId());
$status->_config->set('properties', $properties);
}
return $status;
}
}