admin_main   F
last analyzed

Complexity

Total Complexity 66

Size/Duplication

Total Lines 487
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 123
dl 0
loc 487
rs 3.12
c 5
b 1
f 0
wmc 66

33 Methods

Rating   Name   Duplication   Size   Complexity  
A enable() 0 2 1
A build_remote_uri_select_menu() 0 23 3
A can_submit_data() 0 3 3
A set_page_url() 0 3 1
A view() 0 2 1
A set_settings() 0 2 1
A add() 0 2 1
A submit_or_preview() 0 3 2
A submit_settings() 0 18 2
A s_error_assign_template_vars() 0 5 2
A approve() 0 2 1
A u_action_assign_template_vars() 0 3 1
A check_config() 0 7 2
A is_invalid_form() 0 8 3
A is_form_submitted() 0 3 1
A delete() 0 2 1
A is_added_data_exists() 0 3 2
A get_hidden_fields() 0 8 2
A ppde_first_start() 0 10 2
A trigger_error_data_already_exists() 0 9 2
A add_edit_action_assign_template_vars() 0 8 2
A display() 0 2 1
A ajax_delete_result_message() 0 10 2
A edit() 0 2 1
C main() 0 35 12
A move() 0 2 1
A is_empty_data() 0 10 3
A required_settings() 0 8 3
A set_module_info() 0 5 1
A change() 0 2 1
A force_type() 0 3 3
A set_hidden_fields() 0 7 1
A set_item_id() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like admin_main often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use admin_main, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 *
4
 * PayPal Donation extension for the phpBB Forum Software package.
5
 *
6
 * @copyright (c) 2015-2024 Skouat
7
 * @license GNU General Public License, version 2 (GPL-2.0)
8
 *
9
 */
10
11
namespace skouat\ppde\controller\admin;
12
13
use skouat\ppde\controller\esi_controller;
14
use skouat\ppde\controller\ipn_paypal;
15
16
abstract class admin_main
17
{
18
	protected const SECONDS_IN_A_DAY = 86400;
19
20
	/** @var string */
21
	public $u_action;
22
23
	/** @var array */
24
	protected $args = [];
25
	/** @var \phpbb\config\config */
26
	protected $config;
27
	/** @var \Symfony\Component\DependencyInjection\ContainerInterface */
28
	protected $container;
29
	/** @var string */
30
	protected $id_prefix_name;
31
	/** @var string */
32
	protected $lang_key_prefix;
33
	/** @var \phpbb\language\language */
34
	protected $language;
35
	/** @var \phpbb\log\log */
36
	protected $log;
37
	/** @var string */
38
	protected $module_name;
39
	/** @var \skouat\ppde\actions\locale_icu */
40
	protected $ppde_actions_locale;
41
	/** @var esi_controller */
42
	protected $esi_controller;
43
	/** @var bool */
44
	protected $preview;
45
	/** @var \phpbb\request\request */
46
	protected $request;
47
	/** @var bool */
48
	protected $submit;
49
	/** @var \phpbb\template\template */
50
	protected $template;
51
	/** @var \phpbb\user */
52
	protected $user;
53
54
	public function main(): void
55
	{
56
		$this->u_action_assign_template_vars();
57
		$action = $this->args['action'];
58
		switch ($action)
59
		{
60
			case 'add':
61
			case 'change':
62
			case 'edit':
63
			case 'view':
64
				$this->{$action}();
65
			break;
66
			case 'move_up':
67
			case 'move_down':
68
				$this->move();
69
			break;
70
			case 'activate':
71
			case 'deactivate':
72
				$this->enable();
73
			break;
74
			case 'approve':
75
			case 'delete':
76
				// Use a confirm box routine when approving/deleting an item
77
				if (confirm_box(true))
78
				{
79
					$this->{$action}();
80
					break;
81
				}
82
				confirm_box(false, $this->language->lang($this->lang_key_prefix . '_CONFIRM_OPERATION'), build_hidden_fields($this->get_hidden_fields()));
83
84
				// Clear $action status
85
				$this->args['action'] = $action;
86
			break;
87
			default:
88
				$this->display();
89
		}
90
	}
91
92
	/**
93
	 * Assign u_action output vars for display in the template
94
	 */
95
	protected function u_action_assign_template_vars(): void
96
	{
97
		$this->template->assign_vars(['U_ACTION' => $this->u_action]);
98
	}
99
100
	/**
101
	 * Get hidden fields arguments.
102
	 *
103
	 * @return array Hidden fields arguments.
104
	 */
105
	protected function get_hidden_fields(): array
106
	{
107
		return count($this->args) ? array_merge(
108
			['i'                           => $this->args['i'],
109
			 'mode'                        => $this->args['mode'],
110
			 'action'                      => $this->args['action'],
111
			 $this->id_prefix_name . '_id' => $this->args[$this->id_prefix_name . '_id']],
112
			$this->args['hidden_fields']) : ['id' => '', 'mode' => '', 'action' => ''];
113
	}
114
115
	/**
116
	 * Set the form action URL.
117
	 *
118
	 * @param string $u_action Form action URL.
119
	 */
120
	public function set_page_url($u_action): void
121
	{
122
		$this->u_action = $u_action;
123
	}
124
125
	/**
126
	 * Set hidden fields for the form.
127
	 *
128
	 * @param string $id     Module ID.
129
	 * @param string $mode   Module category.
130
	 * @param string $action Form action.
131
	 */
132
	public function set_hidden_fields($id, $mode, $action): void
133
	{
134
		$this->args = array_merge($this->args, [
135
			'i'             => $id,
136
			'mode'          => $mode,
137
			'action'        => $action,
138
			'hidden_fields' => [],
139
		]);
140
	}
141
142
	public function set_module_info(array $module_info, string $module_name): void
143
	{
144
		$this->id_prefix_name = $module_info['id_prefix_name'] ?? '';
145
		$this->lang_key_prefix = $module_info['lang_key_prefix'] ?? '';
146
		$this->module_name = $module_name;
147
	}
148
149
	/**
150
	 * Set the item ID.
151
	 *
152
	 * @param int $item_id Item ID.
153
	 */
154
	public function set_item_id($item_id): void
155
	{
156
		$this->args[$this->id_prefix_name . '_id'] = (int) $item_id;
157
	}
158
159
	/**
160
	 * Add item for the called controller
161
	 * This method is intended to be overridden by child classes
162
	 */
163
	protected function add(): void
164
	{
165
	}
166
167
	/**
168
	 * Approve an item.
169
	 * This method is intended to be overridden by child classes
170
	 */
171
	protected function approve(): void
172
	{
173
	}
174
175
	/**
176
	 * Change item details for the called controller
177
	 * This method is intended to be overridden by child classes
178
	 */
179
	protected function change(): void
180
	{
181
	}
182
183
	/**
184
	 * Delete item for the called controller
185
	 * This method is intended to be overridden by child classes
186
	 */
187
	protected function delete(): void
188
	{
189
	}
190
191
	/**
192
	 * Display items of the called controller
193
	 * This method is intended to be overridden by child classes
194
	 */
195
	protected function display(): void
196
	{
197
	}
198
199
	/**
200
	 * Edit item on the called controller
201
	 * This method is intended to be overridden by child classes
202
	 */
203
	protected function edit(): void
204
	{
205
	}
206
207
	/**
208
	 * Enable/disable item on the called controller
209
	 * This method is intended to be overridden by child classes
210
	 */
211
	protected function enable(): void
212
	{
213
	}
214
215
	/**
216
	 * Move up/down an item on the called controller
217
	 * This method is intended to be overridden by child classes
218
	 */
219
	protected function move(): void
220
	{
221
	}
222
223
	/**
224
	 * View a selected item on the called controller
225
	 * This method is intended to be overridden by child classes
226
	 */
227
	protected function view(): void
228
	{
229
	}
230
231
	/**
232
	 * Build remote URI select menu options.
233
	 *
234
	 * @param int    $default ID of the default selected option.
235
	 * @param string $type    Type of remote URI ('live' or 'sandbox').
236
	 */
237
	protected function build_remote_uri_select_menu($default, $type): void
238
	{
239
		$type = $this->force_type($type);
240
241
		// Grab the list of remote uri for selected type
242
		$remote_list = ipn_paypal::get_remote_uri();
243
244
		// Process each menu item for pull-down
245
		foreach ($remote_list as $id => $remote)
246
		{
247
			if ($remote['type'] !== $type)
248
			{
249
				continue;
250
			}
251
252
			// Set output block vars for display in the template
253
			$this->template->assign_block_vars('remote_options', [
254
				'REMOTE_ID'   => (int) $id,
255
				'REMOTE_NAME' => $remote['hostname'],
256
				'S_DEFAULT'   => (int) $default === (int) $id,
257
			]);
258
		}
259
		unset ($remote_list, $id);
260
	}
261
262
	/**
263
	 * Enforce the remote URI type.
264
	 *
265
	 * @param string $type Remote URI type.
266
	 * @return string Validated remote URI type.
267
	 */
268
	private function force_type($type): string
269
	{
270
		return $type === 'live' || $type === 'sandbox' ? (string) $type : 'live';
271
	}
272
273
	/**
274
	 * Submit settings.
275
	 */
276
	protected function submit_settings(): void
277
	{
278
		$this->submit = $this->is_form_submitted();
279
280
		// Test if the submitted form is valid
281
		$errors = $this->is_invalid_form('ppde_' . $this->module_name, $this->submit);
282
283
		if ($this->can_submit_data($errors))
284
		{
285
			// Set the options the user configured
286
			$this->set_settings();
287
288
			// Add option settings change action to the admin log
289
			$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_' . $this->lang_key_prefix . '_UPDATED');
290
291
			// Option settings have been updated and logged
292
			// Confirm this to the user and provide link back to previous page
293
			trigger_error($this->language->lang($this->lang_key_prefix . '_SAVED') . adm_back_link($this->u_action));
294
		}
295
	}
296
297
	/**
298
	 * Check if form is submitted.
299
	 *
300
	 * @return bool True if form is submitted, false otherwise.
301
	 */
302
	protected function is_form_submitted(): bool
303
	{
304
		return $this->request->is_set_post('submit');
305
	}
306
307
	/**
308
	 * Check for invalid form submission.
309
	 *
310
	 * @param string $form_name         Name of the form.
311
	 * @param bool   $submit_or_preview Whether the form is submitted or previewed.
312
	 * @return array Errors encountered during form validation.
313
	 */
314
	protected function is_invalid_form($form_name, $submit_or_preview = false): array
315
	{
316
		if ($submit_or_preview && !check_form_key($form_name))
317
		{
318
			return [$this->language->lang('FORM_INVALID')];
319
		}
320
321
		return [];
322
	}
323
324
	/**
325
	 * Check if data can be submitted.
326
	 *
327
	 * @param array $errors Errors encountered during form validation.
328
	 * @return bool True if data can be submitted, false otherwise.
329
	 */
330
	protected function can_submit_data(array $errors): bool
331
	{
332
		return $this->submit && empty($errors) && !$this->preview;
333
	}
334
335
	/**
336
	 * Set the options for called controller
337
	 * This method is intended to be overridden by child classes
338
	 */
339
	protected function set_settings(): void
340
	{
341
	}
342
343
	/**
344
	 * Trigger error message if data already exists
345
	 *
346
	 * @param \skouat\ppde\entity\main $entity Entity object to check for existing data.
347
	 */
348
	protected function trigger_error_data_already_exists(\skouat\ppde\entity\main $entity): void
349
	{
350
		if ($this->is_added_data_exists($entity))
351
		{
352
			// Show user warning for an already exist page and provide link back to the edit page
353
			$message = $this->language->lang($this->lang_key_prefix . '_EXISTS');
354
			$message .= '<br><br>';
355
			$message .= $this->language->lang($this->lang_key_prefix . '_GO_TO_PAGE', '<a href="' . $this->u_action . '&amp;action=edit&amp;' . $this->id_prefix_name . '_id=' . $entity->get_id() . '">&raquo; ', '</a>');
356
			trigger_error($message . adm_back_link($this->u_action), E_USER_WARNING);
357
		}
358
	}
359
360
	/**
361
	 * Check if added data already exists.
362
	 *
363
	 * @param \skouat\ppde\entity\main $entity Entity object to check.
364
	 * @return bool True if the data exists, false otherwise.
365
	 */
366
	protected function is_added_data_exists(\skouat\ppde\entity\main $entity): bool
367
	{
368
		return $entity->data_exists($entity->build_sql_data_exists()) && $this->request->variable('action', '') === 'add';
369
	}
370
371
	/**
372
	 * Check some settings before submitting data
373
	 *
374
	 * @param \skouat\ppde\entity\main $entity            Entity object to check.
375
	 * @param string                   $field_name        Name of the field to check.
376
	 * @param string|int               $value_cmp         Value to compare against for emptiness.
377
	 * @param bool                     $submit_or_preview Whether the form has been submitted or previewed (default: false).
378
	 * @return array Errors encountered during empty data check.
379
	 */
380
	protected function is_empty_data(\skouat\ppde\entity\main $entity, $field_name, $value_cmp, $submit_or_preview = false): array
381
	{
382
		$errors = [];
383
384
		if ($submit_or_preview && $entity->{'get_' . $field_name}() == $value_cmp)
385
		{
386
			$errors[] = $this->language->lang($this->lang_key_prefix . '_EMPTY_' . strtoupper($field_name));
387
		}
388
389
		return $errors;
390
	}
391
392
	/**
393
	 * Check if the form has been submitted or previewed.
394
	 *
395
	 * @param bool $submit  Submission status (default: false).
396
	 * @param bool $preview Preview status (default: false).
397
	 * @return bool True if submitted or previewed, false otherwise.
398
	 */
399
	protected function submit_or_preview(bool $submit = false, bool $preview = false): bool
400
	{
401
		return $submit || $preview;
402
	}
403
404
	/**
405
	 * Send AJAX delete result message.
406
	 *
407
	 * @param string $message Message to send in the response.
408
	 */
409
	protected function ajax_delete_result_message($message = ''): void
410
	{
411
		if ($this->request->is_ajax())
412
		{
413
			$json_response = new \phpbb\json_response;
414
			$json_response->send([
415
				'MESSAGE_TITLE' => $this->language->lang('INFORMATION'),
416
				'MESSAGE_TEXT'  => $message,
417
				'REFRESH_DATA'  => [
418
					'time' => 3,
419
				],
420
			]);
421
		}
422
	}
423
424
	/**
425
	 * Assign add/edit action output vars for display in the template
426
	 *
427
	 * @param string  $type Action type: 'add' or 'edit'
428
	 * @param integer $id   Identifier to Edit. If action = add, then let to '0'.
429
	 */
430
	protected function add_edit_action_assign_template_vars($type, $id = 0): void
431
	{
432
		$id_action = !empty($id) ? '&amp;' . $this->id_prefix_name . '_id=' . (int) $id : '';
433
434
		$this->template->assign_vars([
435
			'S_ADD_EDIT' => true,
436
			'U_ACTION'   => $this->u_action . '&amp;action=' . $type . $id_action,
437
			'U_BACK'     => $this->u_action,
438
		]);
439
	}
440
441
	/**
442
	 * Assign error output vars for display in the template
443
	 *
444
	 * @param array $errors Array of error messages.
445
	 */
446
	protected function s_error_assign_template_vars($errors): void
447
	{
448
		$this->template->assign_vars([
449
			'S_ERROR'   => (bool) count($errors),
450
			'ERROR_MSG' => (count($errors)) ? implode('<br>', $errors) : '',
451
		]);
452
	}
453
454
	/**
455
	 * Check and return a config value with type enforcement.
456
	 *
457
	 * @param mixed  $config  The config value to check.
458
	 * @param string $type    The desired data type (e.g., 'boolean', 'integer', 'string').
459
	 * @param mixed  $default The default value to return if the config value is not set.
460
	 * @return mixed The config value or the default value if not set, with the enforced type.
461
	 */
462
	protected function check_config($config, string $type = 'boolean', $default = '')
463
	{
464
		// We're using settype to enforce data types
465
		settype($config, $type);
466
		settype($default, $type);
467
468
		return $config ?: $default;
469
	}
470
471
	/**
472
	 * Check required settings and trigger warning if missing.
473
	 *
474
	 * @param mixed $settings  The settings value to check.
475
	 * @param bool  $depend_on The condition that determines if the settings are required.
476
	 * @return mixed The settings value if the condition is met or settings are not empty; otherwise, triggers an error and terminates.
477
	 */
478
	protected function required_settings($settings, $depend_on)
479
	{
480
		if (empty($settings) && (bool) $depend_on === true)
481
		{
482
			trigger_error($this->language->lang($this->lang_key_prefix . '_MISSING') . adm_back_link($this->u_action), E_USER_WARNING);
483
		}
484
485
		return $settings;
486
	}
487
488
	/**
489
	 * Perform first-start checks and setup for the extension.
490
	 *
491
	 * @throws \ReflectionException
492
	 */
493
	protected function ppde_first_start(): void
494
	{
495
		if ($this->config['ppde_first_start'])
496
		{
497
			$this->esi_controller->set_curl_info();
498
			$this->esi_controller->set_remote_detected();
499
			$this->esi_controller->check_tls();
500
			$this->ppde_actions_locale->set_intl_info();
501
			$this->ppde_actions_locale->set_intl_detected();
502
			$this->config->set('ppde_first_start', '0');
503
		}
504
	}
505
}
506