1 | <?php |
||||||
2 | /** |
||||||
3 | * EGgroupware admin - UI for adding custom fields |
||||||
4 | * |
||||||
5 | * @link http://www.egroupware.org |
||||||
6 | * @author Ralf Becker <RalfBecker-AT-outdoor-training.de> |
||||||
7 | * @author Cornelius Weiss <nelius-AT-von-und-zu-weiss.de> |
||||||
8 | * @package admin |
||||||
9 | * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License |
||||||
10 | * @version $Id$ |
||||||
11 | */ |
||||||
12 | |||||||
13 | use EGroupware\Api; |
||||||
14 | use EGroupware\Api\Framework; |
||||||
15 | use EGroupware\Api\Etemplate; |
||||||
16 | |||||||
17 | /** |
||||||
18 | * Customfields class - manages customfield definitions in egw_config table |
||||||
19 | * |
||||||
20 | * The repository name (config_name) is 'customfields'. |
||||||
21 | * |
||||||
22 | * Applications can have customfields by sub-type by having a template |
||||||
23 | * named '<appname>.admin.types'. See admin.customfields.types as an |
||||||
24 | * example, but the template can even be empty if types are handled by the |
||||||
25 | * application in another way. |
||||||
26 | * |
||||||
27 | * Applications can extend this class to customize the custom fields and handle |
||||||
28 | * extra information from the above template by extending and implementing |
||||||
29 | * update() and app_index(). |
||||||
30 | */ |
||||||
31 | class admin_customfields |
||||||
32 | { |
||||||
33 | |||||||
34 | /** |
||||||
35 | * appname of app which want to add / edit its customfields |
||||||
36 | * |
||||||
37 | * @var string |
||||||
38 | */ |
||||||
39 | var $appname; |
||||||
40 | |||||||
41 | /** |
||||||
42 | * Allow custom fields to be restricted to certain users/groups |
||||||
43 | */ |
||||||
44 | protected $use_private = false; |
||||||
45 | |||||||
46 | /** |
||||||
47 | * userdefiened types e.g. type of infolog |
||||||
48 | * |
||||||
49 | * @var array |
||||||
50 | */ |
||||||
51 | var $types2 = array(); |
||||||
52 | var $content_types,$fields; |
||||||
53 | |||||||
54 | /** |
||||||
55 | * Does App uses content-types |
||||||
56 | * |
||||||
57 | * @var boolean |
||||||
58 | */ |
||||||
59 | protected $manage_content_types = false; |
||||||
60 | |||||||
61 | /** |
||||||
62 | * Currently selected content type (if used by app) |
||||||
63 | * @var string |
||||||
64 | */ |
||||||
65 | protected $content_type = null; |
||||||
66 | |||||||
67 | var $public_functions = array( |
||||||
68 | 'index' => true, |
||||||
69 | 'edit' => True |
||||||
70 | ); |
||||||
71 | /** |
||||||
72 | * Instance of etemplate class |
||||||
73 | * |
||||||
74 | * @var etemplate |
||||||
75 | */ |
||||||
76 | var $tmpl; |
||||||
77 | |||||||
78 | /** |
||||||
79 | * @var Description of the options or value format for each cf_type |
||||||
0 ignored issues
–
show
|
|||||||
80 | */ |
||||||
81 | public static $type_option_help = array( |
||||||
82 | 'search' => 'set get_rows, get_title and id_field, or use @path to read options from a file in EGroupware directory', |
||||||
83 | 'select' => 'each value is a line like id[=label], or use @path to read options from a file in EGroupware directory', |
||||||
84 | 'radio' => 'each value is a line like id[=label], or use @path to read options from a file in EGroupware directory', |
||||||
85 | 'button' => 'each value is a line like label=[javascript]' |
||||||
86 | ); |
||||||
87 | |||||||
88 | /** |
||||||
89 | * Custom fields can also have length and rows set, but these are't used for all types |
||||||
90 | * If not set to true here, the field will be disabled when selecting the type |
||||||
91 | */ |
||||||
92 | public static $type_attribute_flags = array( |
||||||
93 | 'text' => array('cf_len' => true, 'cf_rows' => true), |
||||||
94 | 'float' => array('cf_len' => true), |
||||||
95 | 'label' => array('cf_values' => true), |
||||||
96 | 'select' => array('cf_len' => false, 'cf_rows' => true, 'cf_values' => true), |
||||||
97 | 'date' => array('cf_len' => true, 'cf_rows' => false, 'cf_values' => true), |
||||||
98 | 'date-time' => array('cf_len' => true, 'cf_rows' => false, 'cf_values' => true), |
||||||
99 | 'select-account' => array('cf_len' => false, 'cf_rows' => true), |
||||||
100 | 'htmlarea' => array('cf_len' => true, 'cf_rows' => true), |
||||||
101 | 'button' => array('cf_values' => true), |
||||||
102 | 'ajax_select' => array('cf_values' => true), |
||||||
103 | 'radio' => array('cf_values' => true), |
||||||
104 | 'checkbox' => array('cf_values' => true), |
||||||
105 | 'filemanager' => array('cf_values' => true), |
||||||
106 | ); |
||||||
107 | |||||||
108 | /** |
||||||
109 | * Constructor |
||||||
110 | * |
||||||
111 | * @param string $appname |
||||||
112 | */ |
||||||
113 | function __construct($appname='') |
||||||
114 | { |
||||||
115 | if (($this->appname = $appname)) |
||||||
116 | { |
||||||
117 | $this->fields = Api\Storage\Customfields::get($this->appname,true); |
||||||
118 | $this->content_types = Api\Config::get_content_types($this->appname); |
||||||
119 | } |
||||||
120 | $this->so = new Api\Storage\Base('phpgwapi','egw_customfields',null,'',true); |
||||||
0 ignored issues
–
show
|
|||||||
121 | } |
||||||
122 | |||||||
123 | /** |
||||||
124 | * List custom fields |
||||||
125 | */ |
||||||
126 | public function index($content = array()) |
||||||
127 | { |
||||||
128 | // determine appname |
||||||
129 | $this->appname = $this->appname ? $this->appname : ($_GET['appname'] ? $_GET['appname'] : ($content['appname'] ? $content['appname'] : false)); |
||||||
0 ignored issues
–
show
It seems like
$this->appname ? $this->...tent['appname'] : false can also be of type false . However, the property $appname is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||||
130 | if(!$this->appname) die(lang('Error! No appname found')); |
||||||
0 ignored issues
–
show
|
|||||||
131 | |||||||
132 | $this->use_private = !isset($_GET['use_private']) || (boolean)$_GET['use_private'] || $content['use_private']; |
||||||
133 | |||||||
134 | // Read fields, constructor doesn't always know appname |
||||||
135 | $this->fields = Api\Storage\Customfields::get($this->appname,true); |
||||||
136 | |||||||
137 | $this->tmpl = new Etemplate(); |
||||||
138 | $this->tmpl->read('admin.customfields'); |
||||||
139 | |||||||
140 | // do we manage content-types? |
||||||
141 | $test = new Etemplate(); |
||||||
142 | if($test->read($this->appname.'.admin.types')) $this->manage_content_types = true; |
||||||
143 | |||||||
144 | // Handle incoming - types, options, etc. |
||||||
145 | if($this->manage_content_types) |
||||||
146 | { |
||||||
147 | if(count($this->content_types) == 0) |
||||||
148 | { |
||||||
149 | $this->content_types = Api\Config::get_content_types($this->appname); |
||||||
150 | } |
||||||
151 | if (count($this->content_types)==0) |
||||||
152 | { |
||||||
153 | // if you define your default types of your app with the search_link hook, they are available here, if no types were found |
||||||
154 | $this->content_types = (array)Api\Link::get_registry($this->appname,'default_types'); |
||||||
155 | } |
||||||
156 | // Set this now, we need to know it for updates |
||||||
157 | $this->content_type = $content['content_types']['types'] ? $content['content_types']['types'] : (array_key_exists(0,$this->content_types) ? $this->content_types[0] : key($this->content_types)); |
||||||
158 | |||||||
159 | // Common type changes - add, delete |
||||||
160 | if($content['content_types']['delete']) |
||||||
161 | { |
||||||
162 | $this->delete_content_type($content); |
||||||
163 | } |
||||||
164 | elseif($content['content_types']['create']) |
||||||
165 | { |
||||||
166 | if(($new_type = $this->create_content_type($content))) |
||||||
167 | { |
||||||
168 | $content['content_types']['types'] = $this->content_type = $new_type; |
||||||
169 | } |
||||||
170 | unset($content['content_types']['create']); |
||||||
171 | unset($content['content_types']['name']); |
||||||
172 | } |
||||||
173 | // No common type change and type didn't change, try an update to check new type statuses |
||||||
174 | elseif($this->content_type && is_array($content) && $this->content_type == $content['old_content_type']) |
||||||
175 | { |
||||||
176 | $this->update($content); |
||||||
177 | } |
||||||
178 | } |
||||||
179 | |||||||
180 | // Custom field deleted from nextmatch |
||||||
181 | if($content['nm']['action'] == 'delete') |
||||||
182 | { |
||||||
183 | foreach($this->fields as $name => $data) |
||||||
184 | { |
||||||
185 | if(in_array($data['id'],$content['nm']['selected'])) |
||||||
186 | { |
||||||
187 | $cmd = new admin_cmd_customfield( |
||||||
188 | $this->appname, |
||||||
189 | array('id' => $data['id'],'name' => $name), |
||||||
190 | null, |
||||||
191 | $content['nm']['admin_cmd'] |
||||||
192 | ); |
||||||
193 | $cmd->run(); |
||||||
194 | unset($this->fields[$name]); |
||||||
195 | |||||||
196 | Framework::refresh_opener('Deleted', 'admin', $data['id'] /* Conflicts with Api\Accounts 'delete'*/); |
||||||
197 | } |
||||||
198 | } |
||||||
199 | } |
||||||
200 | |||||||
201 | $content['nm']= Api\Cache::getSession('admin', 'customfield-index'); |
||||||
202 | if (!is_array($content['nm'])) |
||||||
203 | { |
||||||
204 | // Initialize nextmatch |
||||||
205 | $content['nm'] = array( |
||||||
206 | 'get_rows' => 'admin.admin_customfields.get_rows', |
||||||
207 | 'no_cat' => 'true', |
||||||
208 | 'no_filter' => 'true', |
||||||
209 | 'no_filter2' => 'true', |
||||||
210 | 'row_id' => 'cf_id', |
||||||
211 | 'order' => 'cf_order',// IO name of the column to sort |
||||||
212 | 'sort' => 'ASC',// IO direction of the sort: 'ASC' or 'DESC' |
||||||
213 | 'actions' => $this->get_actions() |
||||||
214 | ); |
||||||
215 | } |
||||||
216 | $content['nm']['appname'] = $this->appname; |
||||||
217 | $content['nm']['use_private'] = $this->use_private; |
||||||
218 | |||||||
219 | // Set up sub-types |
||||||
220 | if($this->manage_content_types) |
||||||
221 | { |
||||||
222 | foreach($this->content_types as $type => $entry) |
||||||
223 | { |
||||||
224 | if(!is_array($entry)) |
||||||
225 | { |
||||||
226 | $this->content_types[$type] = array('name' => $entry); |
||||||
227 | $entry = $this->content_types[$type]; |
||||||
228 | } |
||||||
229 | $this->types2[$type] = $entry['name']; |
||||||
230 | } |
||||||
231 | $sel_options['types'] = $sel_options['cf_type2'] = $this->types2; |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
232 | |||||||
233 | $content['type_template'] = $this->appname . '.admin.types'; |
||||||
234 | $content['content_types']['appname'] = $this->appname; |
||||||
235 | |||||||
236 | $content['content_type_options'] = $this->content_types[$this->content_type]['options']; |
||||||
237 | $content['content_type_options']['type'] = $this->types2[$this->content_type]; |
||||||
238 | if ($this->content_types[$this->content_type]['non_deletable']) |
||||||
239 | { |
||||||
240 | $content['content_types']['non_deletable'] = true; |
||||||
241 | } |
||||||
242 | if ($this->content_types['']['no_add']) |
||||||
243 | { |
||||||
244 | $content['content_types']['no_add'] = true; |
||||||
245 | } |
||||||
246 | if ($content['content_types']['non_deletable'] && $content['content_types']['no_add']) |
||||||
247 | { |
||||||
248 | // Hide the whole line if you can't add or delete |
||||||
249 | $content['content_types']['no_edit_types'] = true; |
||||||
250 | } |
||||||
251 | // do NOT allow to delete original contact content-type for addressbook, |
||||||
252 | // as it only creates support problems as users incidently delete it |
||||||
253 | if ($this->appname == 'addressbook' && $this->content_type == 'n') |
||||||
254 | { |
||||||
255 | $readonlys['content_types']['delete'] = true; |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
256 | } |
||||||
257 | $content['nm']['type2'] = true; |
||||||
258 | } |
||||||
259 | else |
||||||
260 | { |
||||||
261 | // Disable content types |
||||||
262 | $this->tmpl->disableElement('content_types', true); |
||||||
263 | } |
||||||
264 | $preserve = array( |
||||||
265 | 'appname' => $this->appname, |
||||||
266 | 'use_private' => $this->use_private, |
||||||
267 | 'old_content_type' => $this->content_type |
||||||
268 | ); |
||||||
269 | |||||||
270 | // Allow extending app a change to change content before display |
||||||
271 | $readonlys = null; |
||||||
272 | static::app_index($content, $sel_options, $readonlys, $preserve); |
||||||
0 ignored issues
–
show
The method
admin_customfields::app_index() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
273 | |||||||
274 | // Make sure app css & lang get loaded, extending app might cause et2 to miss it |
||||||
275 | Framework::includeCSS('admin','app'); |
||||||
276 | Api\Translation::add_app('admin'); |
||||||
277 | |||||||
278 | // Set app to admin to make sure actions are correctly loaded into admin |
||||||
279 | $GLOBALS['egw_info']['flags']['currentapp'] = 'admin'; |
||||||
280 | $GLOBALS['egw_info']['flags']['app_header'] = $GLOBALS['egw_info']['apps'][$this->appname]['title'].' - '.lang('Custom fields'); |
||||||
281 | |||||||
282 | // Some logic to make sure extending class (if there is one) gets called |
||||||
283 | // when etemplate2 comes back instead of parent class |
||||||
284 | $exec = get_class() == get_called_class() || get_called_class() == 'customfields' ? |
||||||
285 | 'admin.admin_customfields.index' : $this->appname . '.' . get_called_class() . '.index'; |
||||||
286 | |||||||
287 | $this->tmpl->exec($exec,$content,$sel_options,$readonlys,$preserve); |
||||||
288 | } |
||||||
289 | |||||||
290 | /** |
||||||
291 | * Delete a type over ajax. |
||||||
292 | * |
||||||
293 | * Used when Policy is involved, otherwise things go normally |
||||||
294 | * |
||||||
295 | * @param array $content |
||||||
296 | * @param string $etemplate_exec_id to check against CSRF |
||||||
297 | */ |
||||||
298 | public function ajax_delete_type($content, $etemplate_exec_id) |
||||||
299 | { |
||||||
300 | Api\Etemplate\Request::csrfCheck($etemplate_exec_id, __METHOD__, func_get_args()); |
||||||
301 | |||||||
302 | // Read fields |
||||||
303 | $this->appname = $content['appname']; |
||||||
304 | $this->fields = Api\Storage\Customfields::get($content['appname'],true); |
||||||
305 | $this->content_types = Api\Config::get_content_types($content['appname']); |
||||||
306 | $this->delete_content_type($content); |
||||||
307 | } |
||||||
308 | |||||||
309 | /** |
||||||
310 | * Check selectbox values to match regular expression in et2_widget_selectbox.js: _is_multiple_regexp |
||||||
311 | * |
||||||
312 | * If values do not match, comma-separated values are not split by comma! |
||||||
313 | */ |
||||||
314 | const CHECK_MULTISELCT_VALUE = '/^[0-9A-Za-z\/_ -]+$/'; |
||||||
315 | |||||||
316 | /** |
||||||
317 | * Edit/Create Custom fields with type |
||||||
318 | * |
||||||
319 | * @author Ralf Becker <ralfbecker-AT-outdoor-training.de> |
||||||
320 | * @param array $content Content from the eTemplate Exec |
||||||
321 | */ |
||||||
322 | function edit($content = null) |
||||||
323 | { |
||||||
324 | $cf_id = $_GET['cf_id'] ? (int)$_GET['cf_id'] : (int)$content['cf_id']; |
||||||
325 | |||||||
326 | // determine appname |
||||||
327 | $this->appname = $this->appname ? $this->appname : ($_GET['appname'] ? $_GET['appname'] : ($content['cf_app'] ? $content['cf_app'] : false)); |
||||||
0 ignored issues
–
show
It seems like
$this->appname ? $this->...ntent['cf_app'] : false can also be of type false . However, the property $appname is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||||
328 | if(!$this->appname) |
||||||
329 | { |
||||||
330 | if($cf_id && $this->so) |
||||||
331 | { |
||||||
332 | $content = $this->so->read($cf_id); |
||||||
333 | $this->appname = $content['cf_app']; |
||||||
334 | } |
||||||
335 | } |
||||||
336 | if(!$this->appname) |
||||||
337 | { |
||||||
338 | die(lang('Error! No appname found')); |
||||||
0 ignored issues
–
show
|
|||||||
339 | } |
||||||
340 | $this->use_private = !isset($_GET['use_private']) || (boolean)$_GET['use_private'] || $content['use_private']; |
||||||
341 | |||||||
342 | // Read fields, constructor doesn't always know appname |
||||||
343 | $this->fields = Api\Storage\Customfields::get($this->appname,true); |
||||||
344 | |||||||
345 | // Update based on info returned from template |
||||||
346 | if (is_array($content)) |
||||||
347 | { |
||||||
348 | $action = @key($content['button']); |
||||||
349 | switch($action) |
||||||
350 | { |
||||||
351 | case 'delete': |
||||||
352 | $field = $this->so->read($cf_id); |
||||||
353 | $cmd = new admin_cmd_customfield($this->appname, array('id' => $cf_id,'name' => $field['cf_name'])); |
||||||
354 | $cmd->run(); |
||||||
355 | Framework::refresh_opener('Deleted', 'admin', $cf_id /* Conflicts with Api\Accounts 'delete'*/); |
||||||
356 | Framework::window_close(); |
||||||
357 | break; |
||||||
358 | case 'save': |
||||||
359 | case 'apply': |
||||||
360 | if(!$cf_id && $this->fields[$content['cf_name']]) |
||||||
361 | { |
||||||
362 | Framework::message(lang("Field '%1' already exists !!!",$content['cf_name']),'error'); |
||||||
0 ignored issues
–
show
The call to
lang() has too many arguments starting with $content['cf_name'] .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
363 | $content['cf_name'] = ''; |
||||||
364 | break; |
||||||
365 | } |
||||||
366 | if(empty($content['cf_label'])) |
||||||
367 | { |
||||||
368 | $content['cf_label'] = $content['cf_name']; |
||||||
369 | } |
||||||
370 | if (!empty($content['cf_values'])) |
||||||
371 | { |
||||||
372 | $values = array(); |
||||||
373 | if($content['cf_values'][0] === '@') |
||||||
374 | { |
||||||
375 | $values['@'] = substr($content['cf_values'], $content['cf_values'][1] === '=' ? 2:1); |
||||||
376 | } |
||||||
377 | else |
||||||
378 | { |
||||||
379 | foreach(explode("\n",trim($content['cf_values'])) as $idx => $line) |
||||||
380 | { |
||||||
381 | list($var_raw,$value) = explode('=',trim($line),2); |
||||||
382 | $var = trim($var_raw); |
||||||
383 | if (!preg_match(self::CHECK_MULTISELCT_VALUE, $var) && !($idx == 0 && !$var && $value)) |
||||||
384 | { |
||||||
385 | Api\Etemplate::set_validation_error('cf_values', |
||||||
386 | lang('Invalid value "%1", use only:', $var)."\n". |
||||||
387 | preg_replace('/^.*\[([^]]+)\].*$/', '$1', self::CHECK_MULTISELCT_VALUE)); |
||||||
388 | $action = 'apply'; // do not close the window to show validation error |
||||||
389 | if (!$cf_id) break 2; // only stop storing of new CFs |
||||||
390 | } |
||||||
391 | $values[$var] = trim($value)==='' ? $var : $value; |
||||||
392 | } |
||||||
393 | } |
||||||
394 | $content['cf_values'] = $values; |
||||||
395 | } |
||||||
396 | $update_content = array(); |
||||||
397 | foreach($content as $key => $value) |
||||||
398 | { |
||||||
399 | if(substr($key,0,3) == 'cf_') |
||||||
400 | { |
||||||
401 | $update_content[substr($key,3)] = $value; |
||||||
402 | } |
||||||
403 | } |
||||||
404 | $cmd = new admin_cmd_customfield($this->appname, $update_content,null,$content['admin_cmd']); |
||||||
405 | $cmd->run(); |
||||||
406 | if(!$cf_id) |
||||||
407 | { |
||||||
408 | $this->fields = Api\Storage\Customfields::get($this->appname,true); |
||||||
409 | $cf_id = (int)$this->fields[$content['cf_name']]['id']; |
||||||
410 | } |
||||||
411 | Framework::refresh_opener(lang('Entry saved'), 'admin', $cf_id, 'edit'); |
||||||
412 | if ($action != 'save') |
||||||
413 | { |
||||||
414 | break; |
||||||
415 | } |
||||||
416 | //fall through |
||||||
417 | case 'cancel': |
||||||
418 | Framework::window_close(); |
||||||
419 | } |
||||||
420 | } |
||||||
421 | else |
||||||
422 | { |
||||||
423 | $content['use_private'] = !isset($_GET['use_private']) || (boolean)$_GET['use_private']; |
||||||
424 | } |
||||||
425 | |||||||
426 | |||||||
427 | // do we manage content-types? |
||||||
428 | $test = new Etemplate(); |
||||||
429 | if($test->read($this->appname.'.admin.types')) $this->manage_content_types = true; |
||||||
430 | |||||||
431 | $this->tmpl = new Etemplate(); |
||||||
432 | $this->tmpl->read('admin.customfield_edit'); |
||||||
433 | |||||||
434 | Api\Translation::add_app('infolog'); // til we move the translations |
||||||
435 | |||||||
436 | $GLOBALS['egw_info']['flags']['app_header'] = $GLOBALS['egw_info']['apps'][$this->appname]['title'].' - '.lang('Custom fields'); |
||||||
437 | $sel_options = array(); |
||||||
438 | $readonlys = array(); |
||||||
439 | |||||||
440 | //echo 'customfields=<pre style="text-align: left;">'; print_r($this->fields); echo "</pre>\n"; |
||||||
441 | $content['cf_order'] = (count($this->fields)+1) * 10; |
||||||
442 | $content['use_private'] = $this->use_private; |
||||||
443 | |||||||
444 | if($cf_id) |
||||||
445 | { |
||||||
446 | $content = array_merge($content, $this->so->read($cf_id)); |
||||||
447 | $this->appname = $content['cf_app']; |
||||||
448 | if($content['cf_private']) |
||||||
449 | { |
||||||
450 | $content['cf_private'] = explode(',',$content['cf_private']); |
||||||
451 | } |
||||||
452 | if($content['cf_name']) |
||||||
453 | { |
||||||
454 | $readonlys['cf_name'] = true; |
||||||
455 | } |
||||||
456 | $content['cf_values'] = json_decode($content['cf_values'], true); |
||||||
457 | } |
||||||
458 | else |
||||||
459 | { |
||||||
460 | $readonlys['button[delete]'] = true; |
||||||
461 | } |
||||||
462 | if (is_array($content['cf_values'])) |
||||||
463 | { |
||||||
464 | $values = ''; |
||||||
465 | foreach($content['cf_values'] as $var => $value) |
||||||
466 | { |
||||||
467 | $values .= (!empty($values) ? "\n" : '').$var.'='.$value; |
||||||
468 | } |
||||||
469 | $content['cf_values'] = $values; |
||||||
470 | } |
||||||
471 | |||||||
472 | // Show sub-type row, and get types |
||||||
473 | if($this->manage_content_types) |
||||||
474 | { |
||||||
475 | if(count($this->content_types) == 0) |
||||||
476 | { |
||||||
477 | $this->content_types = Api\Config::get_content_types($this->appname); |
||||||
478 | } |
||||||
479 | if (count($this->content_types)==0) |
||||||
480 | { |
||||||
481 | // if you define your default types of your app with the search_link hook, they are available here, if no types were found |
||||||
482 | $this->content_types = (array)Api\Link::get_registry($this->appname, 'default_types'); |
||||||
483 | } |
||||||
484 | foreach($this->content_types as $type => $entry) |
||||||
485 | { |
||||||
486 | $this->types2[$type] = is_array($entry) ? $entry['name'] : $entry; |
||||||
487 | } |
||||||
488 | $sel_options['cf_type2'] = $this->types2; |
||||||
489 | } |
||||||
490 | else |
||||||
491 | { |
||||||
492 | $content['no_types'] = true; |
||||||
493 | } |
||||||
494 | |||||||
495 | // Include type-specific value help |
||||||
496 | foreach(self::$type_option_help as $key => $value) |
||||||
497 | { |
||||||
498 | $content['options'][$key] = lang($value); |
||||||
499 | } |
||||||
500 | $content['statustext'] = $content['options'][$content['cf_type']]; |
||||||
501 | $content['attributes'] = self::$type_attribute_flags; |
||||||
502 | |||||||
503 | $this->tmpl->exec('admin.admin_customfields.edit',$content,$sel_options,$readonlys,array( |
||||||
504 | 'cf_id' => $cf_id, |
||||||
505 | 'cf_app' => $this->appname, |
||||||
506 | 'cf_name' => $content['cf_name'], |
||||||
507 | 'use_private' => $this->use_private, |
||||||
508 | ),2); |
||||||
509 | } |
||||||
510 | |||||||
511 | /** |
||||||
512 | * Allow extending apps a change to interfere and add content to support |
||||||
513 | * their custom template. This is called right before etemplate->exec(). |
||||||
514 | */ |
||||||
515 | protected function app_index(&$content, &$sel_options, &$readonlys, &$preserve) |
||||||
516 | { |
||||||
517 | unset($content, $sel_options, $readonlys, $preserve); // not used, as this is a stub |
||||||
518 | // This is just a stub. |
||||||
519 | } |
||||||
520 | |||||||
521 | /** |
||||||
522 | * Get actions / context menu for index |
||||||
523 | * |
||||||
524 | * Changes here, require to log out, as $content['nm'] get stored in session! |
||||||
525 | * |
||||||
526 | * @return array see nextmatch_widget::egw_actions() |
||||||
527 | */ |
||||||
528 | protected function get_actions() |
||||||
529 | { |
||||||
530 | $actions = array( |
||||||
531 | 'open' => array( // does edit if allowed, otherwise view |
||||||
532 | 'caption' => 'Open', |
||||||
533 | 'default' => true, |
||||||
534 | 'allowOnMultiple' => false, |
||||||
535 | 'url' => 'menuaction=admin.admin_customfields.edit&cf_id=$id&use_private='.$this->use_private, |
||||||
536 | 'popup' => '500x380', |
||||||
537 | 'group' => $group=1, |
||||||
538 | 'disableClass' => 'th', |
||||||
539 | ), |
||||||
540 | 'add' => array( |
||||||
541 | 'caption' => 'Add', |
||||||
542 | 'url' => 'menuaction=admin.admin_customfields.edit&appname='.$this->appname.'&use_private='.$this->use_private, |
||||||
543 | 'popup' => '500x380', |
||||||
544 | 'group' => $group, |
||||||
545 | ), |
||||||
546 | 'delete' => array( |
||||||
547 | 'caption' => 'Delete', |
||||||
548 | 'confirm' => 'Delete this entry', |
||||||
549 | 'confirm_multiple' => 'Delete these entries', |
||||||
550 | 'policy_confirmation' => 'Oh yeah', |
||||||
551 | 'group' => ++$group, |
||||||
552 | 'disableClass' => 'rowNoDelete', |
||||||
553 | ), |
||||||
554 | ); |
||||||
555 | return $actions; |
||||||
556 | } |
||||||
557 | |||||||
558 | function update(&$content) |
||||||
559 | { |
||||||
560 | $this->content_types[$this->content_type]['options'] = $content['content_type_options']; |
||||||
561 | // save changes to repository |
||||||
562 | $this->save_repository(); |
||||||
563 | } |
||||||
564 | |||||||
565 | /** |
||||||
566 | * deletes custom field from customfield definitions |
||||||
567 | */ |
||||||
568 | function delete_field(&$content) |
||||||
569 | { |
||||||
570 | unset($this->fields[key($content['fields']['delete'])]); |
||||||
571 | // save changes to repository |
||||||
572 | $this->save_repository(); |
||||||
573 | } |
||||||
574 | |||||||
575 | function delete_content_type(&$content) |
||||||
576 | { |
||||||
577 | $old = array('types' => $this->content_types); |
||||||
578 | unset($this->content_types[$content['content_types']['types']]); |
||||||
579 | unset($this->status[$content['content_types']['types']]); |
||||||
580 | $cmd = new admin_cmd_config($this->appname,array('types' => $this->content_types), $old, $content['admin_cmd']); |
||||||
581 | $cmd->run(); |
||||||
582 | |||||||
583 | // save changes to repository |
||||||
584 | $this->save_repository(); |
||||||
585 | } |
||||||
586 | |||||||
587 | /** |
||||||
588 | * create a new custom field |
||||||
589 | */ |
||||||
590 | function create_field(&$content) |
||||||
591 | { |
||||||
592 | $new_name = trim($content['fields'][count($content['fields'])-1]['name']); |
||||||
593 | if (empty($new_name) || isset($this->fields[$new_name])) |
||||||
594 | { |
||||||
595 | $content['error_msg'] .= empty($new_name) ? |
||||||
596 | lang('You have to enter a name, to create a new field!!!') : |
||||||
597 | lang("Field '%1' already exists !!!",$new_name); |
||||||
0 ignored issues
–
show
The call to
lang() has too many arguments starting with $new_name .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
598 | } |
||||||
599 | else |
||||||
600 | { |
||||||
601 | $this->fields[$new_name] = $content['fields'][count($content['fields'])-1]; |
||||||
602 | if(!$this->fields[$new_name]['label']) $this->fields[$new_name]['label'] = $this->fields[$new_name]['name']; |
||||||
603 | $this->save_repository(); |
||||||
604 | } |
||||||
605 | } |
||||||
606 | |||||||
607 | /** |
||||||
608 | * Validate and create a new content type |
||||||
609 | * |
||||||
610 | * @param array $content |
||||||
611 | * @return string|boolean New type ID, or false for error |
||||||
612 | */ |
||||||
613 | function create_content_type(&$content) |
||||||
614 | { |
||||||
615 | $new_name = trim($content['content_types']['name']); |
||||||
616 | $new_type = false; |
||||||
617 | if (empty($new_name)) |
||||||
618 | { |
||||||
619 | $this->tmpl->set_validation_error('content_types[name]',lang('you have to enter a name, to create a new type!')); |
||||||
620 | } |
||||||
621 | else |
||||||
622 | { |
||||||
623 | foreach($this->content_types as $type) |
||||||
624 | { |
||||||
625 | if($type['name'] == $new_name) |
||||||
626 | { |
||||||
627 | $this->tmpl->set_validation_error('content_types[name]',lang("type '%1' already exists !!!",$new_name)); |
||||||
0 ignored issues
–
show
The call to
lang() has too many arguments starting with $new_name .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
628 | return false; |
||||||
629 | } |
||||||
630 | } |
||||||
631 | // search free type character |
||||||
632 | for($i=97;$i<=122;$i++) |
||||||
633 | { |
||||||
634 | if (!$this->content_types[chr($i)] && |
||||||
635 | // skip letter of deleted type for addressbook content-types, as it gives SQL error |
||||||
636 | // content-type are lowercase, Api\Contacts::DELETED_TYPE === 'D', but DB is case-insensitive |
||||||
637 | ($this->appname !== 'addressbook' || chr($i) !== strtolower(Api\Contacts::DELETED_TYPE))) |
||||||
638 | { |
||||||
639 | $new_type = chr($i); |
||||||
640 | break; |
||||||
641 | } |
||||||
642 | } |
||||||
643 | $this->content_types[$new_type] = array('name' => $new_name); |
||||||
644 | $this->save_repository(); |
||||||
645 | } |
||||||
646 | return $new_type; |
||||||
647 | } |
||||||
648 | |||||||
649 | /** |
||||||
650 | * save changes to repository |
||||||
651 | */ |
||||||
652 | function save_repository() |
||||||
653 | { |
||||||
654 | //echo '<p>uicustomfields::save_repository() \$this->fields=<pre style="text-aling: left;">'; print_r($this->fields); echo "</pre>\n"; |
||||||
655 | $config = new Api\Config($this->appname); |
||||||
656 | $config->read_repository(); |
||||||
657 | $config->value('types',$this->content_types); |
||||||
658 | $config->save_repository(); |
||||||
659 | } |
||||||
660 | |||||||
661 | /** |
||||||
662 | * get customfields of using application |
||||||
663 | * |
||||||
664 | * @deprecated use Api\Storage\Customfields::get() direct, no need to instanciate this UI class |
||||||
665 | * @author Cornelius Weiss |
||||||
666 | * @param boolean $all_private_too =false should all the private fields be returned too |
||||||
667 | * @return array with customfields |
||||||
668 | */ |
||||||
669 | function get_customfields($all_private_too=false) |
||||||
670 | { |
||||||
671 | return Api\Storage\Customfields::get($this->appname,$all_private_too); |
||||||
672 | } |
||||||
673 | |||||||
674 | /** |
||||||
675 | * get_content_types of using application |
||||||
676 | * |
||||||
677 | * @deprecated use Api\Config::get_content_types() direct, no need to instanciate this UI class |
||||||
678 | * @author Cornelius Weiss |
||||||
679 | * @return array with content-types |
||||||
680 | */ |
||||||
681 | function get_content_types() |
||||||
682 | { |
||||||
683 | return Api\Config::get_content_types($this->appname); |
||||||
684 | } |
||||||
685 | |||||||
686 | /** |
||||||
687 | * Get list of customfields for the nextmatch |
||||||
688 | */ |
||||||
689 | public function get_rows(&$query, &$rows, &$readonlys) |
||||||
690 | { |
||||||
691 | $rows = array(); |
||||||
692 | |||||||
693 | $query['col_filter']['cf_app'] = $query['appname']; |
||||||
694 | $total = $this->so->get_rows($query, $rows, $readonlys); |
||||||
695 | unset($query['col_filter']['cf_app']); |
||||||
696 | |||||||
697 | foreach($rows as &$row) |
||||||
698 | { |
||||||
699 | $row['cf_values'] = json_decode($row['cf_values'], true); |
||||||
700 | if (is_array($row['cf_values'])) |
||||||
701 | { |
||||||
702 | $values = ''; |
||||||
703 | foreach($row['cf_values'] as $var => $value) |
||||||
704 | { |
||||||
705 | $values .= (!empty($values) ? "\n" : '').$var.'='.$value; |
||||||
706 | } |
||||||
707 | $row['cf_values'] = $values; |
||||||
708 | } |
||||||
709 | } |
||||||
710 | return $total; |
||||||
711 | } |
||||||
712 | } |
||||||
713 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths