import
[web.mtrack] / inc / customfield.php
1 <?php # vim:ts=2:sw=2:et:
2
3 class MTrackTicket_CustomField {
4   var $name;
5   var $type;
6   var $label;
7   var $group;
8   var $order = 0;
9   var $default;
10   var $options;
11
12   static function canonName($name) {
13     if (!preg_match("/^x_/", $name)) {
14       $name = "x_$name";
15     }
16     return $name;
17   }
18
19   /** load the field definition from the configuration file */
20   static function load($name) {
21     if (!preg_match("/^x_[a-z_]+$/", $name)) {
22       throw new Exception("invalid field name $name");
23     }
24
25     $field = new MTrackTicket_CustomField;
26     $field->name = $name;
27
28     $field->type  = MTrackConfig::get('ticket.custom', "$name.type");
29     $field->label = MTrackConfig::get('ticket.custom', "$name.label");
30     $field->group = MTrackConfig::get('ticket.custom', "$name.group");
31     $field->order   = (int)MTrackConfig::get('ticket.custom', "$name.order");
32     $field->default = MTrackConfig::get('ticket.custom', "$name.default");
33     $field->options = MTrackConfig::get('ticket.custom', "$name.options");
34
35     return $field;
36   }
37
38   function save() {
39     if (!preg_match("/^x_[a-z_]+$/", $this->name)) {
40       throw new Exception("invalid field name $this->name");
41     }
42     $name = $this->name;
43     MTrackConfig::set('ticket.custom', "$name.type", $this->type);
44     MTrackConfig::set('ticket.custom', "$name.label", $this->label);
45     MTrackConfig::set('ticket.custom', "$name.group", $this->group);
46     MTrackConfig::set('ticket.custom', "$name.order", (int)$this->order);
47     MTrackConfig::set('ticket.custom', "$name.default", $this->default);
48     MTrackConfig::set('ticket.custom', "$name.options", $this->options);
49   }
50
51   function ticketData() {
52     /* compatible with the $FIELDSET data used in web/ticket.php */
53     $data = array(
54       'label' => $this->label,
55       'type' => $this->type,
56     );
57
58     if (strlen($this->default)) {
59       $data['default'] = $this->default;
60     }
61
62     switch ($this->type) {
63       case 'multi':
64       case 'wiki':
65       case 'shortwiki':
66         $data['ownrow'] = true;
67         $data['rows'] = 5;
68         $data['cols'] = 78;
69         break;
70       case 'select':
71       case 'multiselect':
72         $options = array('' => ' --- ');
73         foreach (explode('|', $this->options) as $opt) {
74           $options[$opt] = $opt;
75         }
76         $data['options'] = $options;
77         break;
78     }
79     return $data;
80   }
81 }
82
83 class MTrackTicket_CustomFields
84   implements IMTrackIssueListener
85 {
86   var $fields = array();
87
88   var $field_types = array(
89     'text' => 'Text (single line)',
90     'multi' => 'Text (multi-line)',
91     'wiki' => 'Wiki',
92     'shortwiki' => 'Wiki (shorter height)',
93     'select' => 'Select box (choice of one)',
94 // Don't allow multi-select for now; need a sane way to make the value
95 // into an array.
96 //    'multiselect' => 'Multiple select',
97   );
98
99   function save() {
100     $this->alterSchema();
101
102     $fieldlist = join(',', array_keys($this->fields));
103     MTrackConfig::set('ticket', 'customfields', $fieldlist);
104
105     foreach ($this->fields as $field) {
106       $field->save();
107     }
108   }
109
110   function fieldByName($name, $create = false) {
111     $name = MTrackTicket_CustomField::canonName($name);
112     if (!isset($this->fields[$name]) && $create) {
113       $field = new MTrackTicket_CustomField;
114       $field->name = $name;
115       $this->fields[$name] = $field;
116     } else if (!isset($this->fields[$name])) {
117       return null;
118     }
119     return $this->fields[$name];
120   }
121
122   function deleteField($field) {
123     if (!($field instanceof MTrackTicket_CustomField)) {
124       $field = $this->fieldByName($field);
125     }
126     if (!($field instanceof MTrackTicket_CustomField)) {
127       throw new Exception("can't delete an unknown field");
128     }
129     unset($this->fields[$field->name]);
130   }
131
132   function vetoMilestone(MTrackIssue $issue,
133       MTrackMilestone $ms, $assoc = true) {
134     return true;
135   }
136   function vetoKeyword(MTrackIssue $issue,
137       MTrackKeyword $kw, $assoc = true) {
138     return true;
139   }
140   function vetoComponent(MTrackIssue $issue,
141       MTrackComponent $comp, $assoc = true) {
142     return true;
143   }
144   function vetoProject(MTrackIssue $issue,
145       MTrackProject $proj, $assoc = true) {
146     return true;
147   }
148   function vetoComment(MTrackIssue $issue, $comment) {
149     return true;
150   }
151   function vetoSave(MTrackIssue $issue, $oldFields) {
152     return true;
153   }
154
155   function _orderField($a, $b) {
156     $diff = $a->order - $b->order;
157     if ($diff == 0) {
158       return strnatcasecmp($a->label, $b->label);
159     }
160     return $diff;
161   }
162
163   function getGroupedFields() {
164     $grouped = array();
165     foreach ($this->fields as $field) {
166       $grouped[$field->group][$field->name] = $field;
167     }
168     $result = array();
169     $names = array_keys($grouped);
170     asort($grouped);
171     foreach ($grouped as $name => $group) {
172       uasort($group, array($this, '_orderField'));
173       $result[$name] = $group;
174     }
175     return $result;
176   }
177
178   function augmentFormFields(MTrackIssue $issue, &$fieldset) {
179     $grouped = $this->getGroupedFields();
180     foreach ($grouped as $group) {
181       foreach ($group as $field) {
182         $fieldset[$field->group][$field->name] = $field->ticketData();
183       }
184     }
185   }
186
187   function augmentSaveParams(MTrackIssue $issue, &$params) {
188     foreach ($this->fields as $field) {
189       $params[$field->name] = $issue->{$field->name};
190     }
191   }
192   function augmentIndexerFields(MTrackIssue $issue, &$idx) {
193     foreach ($this->fields as $field) {
194       $idx[$field->name] = $issue->{$field->name};
195     }
196   }
197
198   function applyPOSTData(MTrackIssue $issue, $post) {
199     foreach ($this->fields as $field) {
200       if ($field->type == 'multiselect') {
201         $issue->{$field->name} = join('|', $post[$field->name]);
202       } else {
203         $issue->{$field->name} = $post[$field->name];
204       }
205     }
206   }
207
208   function alterSchema() {
209     $names = array();
210     foreach ($this->fields as $field) {
211       $names[] = $field->name;
212     }
213     $db = MTrackDB::get();
214     try {
215       $db->exec("select " . join(', ', $names) . ' from tickets limit 1');
216     } catch (Exception $e) {
217       foreach ($names as $name) {
218         try {
219           $db->exec("ALTER TABLE tickets add column $name text");
220         } catch (Exception $e) {
221         }
222       }
223     }
224   }
225
226   function __construct() {
227     MTrackIssue::registerListener($this);
228
229     /* read in custom fields from ini */
230     $fieldlist = MTrackConfig::get('ticket', 'customfields');
231     if ($fieldlist) {
232       $fieldlist = preg_split("/\s*,\s*/", $fieldlist);
233       foreach ($fieldlist as $fieldname) {
234         $field = MTrackTicket_CustomField::load($fieldname);
235         $this->fields[$field->name] = $field;
236       }
237     }
238   }
239
240   static $me = null;
241   static function getInstance() {
242     if (self::$me !== null) {
243       return self::$me;
244     }
245     self::$me = new MTrackTicket_CustomFields;
246     return self::$me;
247   }
248 }
249
250 MTrackTicket_CustomFields::getInstance();
251
252