Completed
Push — master ( 8f300a...2b4e18 )
by Matt
02:06
created

dbtool_module::process()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 27
rs 8.5806
cc 4
eloc 13
nc 3
nop 3
1
<?php
2
/**
3
*
4
* Database Optimize & Repair Tool
5
*
6
* @copyright (c) 2013 Matt Friedman
7
* @license GNU General Public License, version 2 (GPL-2.0)
8
*
9
*/
10
11
namespace vse\dbtool\acp;
12
13
/**
14
* @package acp
15
*/
16
class dbtool_module
17
{
18
	/** @var \phpbb\cache\driver\driver_interface */
19
	protected $cache;
20
21
	/** @var \phpbb\config\config */
22
	protected $config;
23
24
	/** @var \phpbb\db\driver\driver_interface */
25
	protected $db;
26
27
	/** @var \phpbb\log\log */
28
	protected $log;
29
30
	/** @var \phpbb\php\ini */
31
	protected $php_ini;
32
33
	/** @var \phpbb\request\request */
34
	protected $request;
35
36
	/** @var \phpbb\template\template */
37
	protected $template;
38
39
	/** @var \phpbb\user */
40
	protected $user;
41
42
	/** @var string */
43
	public $u_action;
44
45
	/**
46
	* Constructor
47
	*/
48
	public function __construct()
49
	{
50
		global $cache, $config, $db, $phpbb_log, $request, $template, $user;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
51
52
		$this->cache = $cache;
53
		$this->config = $config;
54
		$this->db = $db;
55
		$this->log = $phpbb_log;
56
		$this->request = $request;
57
		$this->template = $template;
58
		$this->user = $user;
59
		$this->php_ini = new \phpbb\php\ini();
60
61
		$this->user->add_lang_ext('vse/dbtool', 'dbtool_acp');
62
	}
63
64
	/**
65
	* Main ACP module
66
	*
67
	* @param int    $id
68
	* @param string $mode
69
	* @access public
70
	*/
71
	public function main($id, $mode)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $mode is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
72
	{
73
		$this->tpl_name = 'acp_dbtool';
0 ignored issues
show
Bug introduced by
The property tpl_name does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
74
		$this->page_title = 'ACP_OPTIMIZE_REPAIR';
0 ignored issues
show
Bug introduced by
The property page_title does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
75
76
		if (!$this->is_mysql())
77
		{
78
			trigger_error($this->user->lang('WARNING_MYSQL'), E_USER_WARNING);
79
		}
80
81
		if ($this->request->is_set_post('submit'))
82
		{
83
			$this->run_tool();
84
		}
85
86
		$this->display_tables();
87
	}
88
89
	/**
90
	* Run database tool
91
	*
92
	* @return null
93
	* @access protected
94
	*/
95
	protected function run_tool()
96
	{
97
		$operation = $this->request->variable('operation', '');
98
		$marked = $this->request->variable('mark', array(''));
99
		$disable_board = (!$this->config['board_disable']) ? $this->request->variable('disable_board', 0) : 0;
100
101
		if (confirm_box(true))
102
		{
103
			if (!sizeof($marked))
104
			{
105
				trigger_error($this->user->lang('TABLE_ERROR') . adm_back_link($this->u_action), E_USER_WARNING);
106
			}
107
108
			$operation = strtoupper($operation);
109
			$tables = implode(', ', $marked);
110
111
			if ($this->is_valid_operation($operation))
112
			{
113
				$result = $this->process($operation, $tables, $disable_board);
114
				trigger_error($this->user->lang($operation . '_SUCCESS') . $result . adm_back_link($this->u_action));
115
			}
116
		}
117
		else
118
		{
119
			confirm_box(false, $this->user->lang('CONFIRM_OPERATION'), build_hidden_fields(array(
120
				'submit'		=> 1,
121
				'operation'		=> $operation,
122
				'mark'			=> $marked,
123
				'disable_board'	=> $disable_board,
124
			)));
125
		}
126
	}
127
128
	/**
129
	* Perform table SQL query and return any messages
130
	*
131
	* @param string $operation     OPTIMIZE, REPAIR, or CHECK
132
	* @param string $tables        Comma delineated string of all tables to be processed
133
	* @param int    $disable_board The user's option to disable the board during run time
134
	* @return string Any errors or status information
135
	* @access protected
136
	*/
137
	protected function process($operation, $tables, $disable_board = 0)
138
	{
139
		$this->extend_execution_limits();
140
141
		$this->disable_board($disable_board, true);
142
143
		$message = '<br />';
144
		$result = $this->db->sql_query($operation . ' TABLE ' . $this->db->sql_escape($tables));
145
		while ($row = $this->db->sql_fetchrow($result))
146
		{
147
			// Build a message only for optimize/repair errors, or if check table is run
148
			if ((in_array(strtolower($row['Msg_type']), array('error', 'info', 'note', 'warning'))) || $operation == 'CHECK')
149
			{
150
				$message .= '<br />' . substr($row['Table'], (strpos($row['Table'], '.') + 1)) . ' ... ' . $row['Msg_type'] . ': ' . $row['Msg_text'];
151
			}
152
		}
153
		$this->db->sql_freeresult($result);
154
155
		$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, $operation . '_LOG', time(), array($tables));
156
157
		$this->disable_board($disable_board, false);
158
159
		// Clear cache to ensure board is re-enabled for all users
160
		$this->cache->purge();
161
162
		return $message;
163
	}
164
165
	/**
166
	* Generate Show Table Data
167
	*
168
	* @return null
169
	* @access protected
170
	*/
171
	protected function display_tables()
172
	{
173
		$total_data_size = $total_data_free = 0;
174
175
		$tables = $this->db->sql_query('SHOW TABLE STATUS');
176
177
		while ($table = $this->db->sql_fetchrow($tables))
178
		{
179
			$table['Engine'] = (!empty($table['Type']) ? $table['Type'] : $table['Engine']);
180
			if ($this->is_valid_engine($table['Engine']))
181
			{
182
				// Data_free should always be 0 for InnoDB tables
183
				if ($this->is_innodb($table['Engine']))
184
				{
185
					$table['Data_free'] = 0;
186
				}
187
188
				$data_size = $table['Data_length'] + $table['Index_length'];
189
				$total_data_size = $total_data_size + $data_size;
190
				$total_data_free = $total_data_free + $table['Data_free'];
191
192
				$this->template->assign_block_vars('tables', array(
193
					'TABLE_NAME'	=> $table['Name'],
194
					'TABLE_TYPE'	=> $table['Engine'],
195
					'DATA_SIZE'		=> $this->file_size($data_size),
196
					'DATA_FREE'		=> $this->file_size($table['Data_free']),
197
					'S_OVERHEAD'	=> (bool) $table['Data_free'],
198
				));
199
			}
200
		}
201
		$this->db->sql_freeresult($tables);
202
203
		$this->template->assign_vars(array(
204
			'TOTAL_DATA_SIZE' => $this->file_size($total_data_size),
205
			'TOTAL_DATA_FREE' => $this->file_size($total_data_free),
206
			'U_ACTION' => $this->u_action,
207
		));
208
	}
209
210
	/**
211
	* Is the database using MySQL
212
	*
213
	* @return bool True if MySQL, false otherwise
214
	* @access protected
215
	*/
216
	protected function is_mysql()
217
	{
218
		return $this->db->get_sql_layer() == 'mysql4' || $this->db->get_sql_layer() == 'mysqli';
219
	}
220
221
	/**
222
	* Is requested operation to optimize, repair or check tables
223
	*
224
	* @param string $operation The name of the operation
225
	* @return bool True if valid operation, false otherwise
226
	* @access public
227
	*/
228
	public function is_valid_operation($operation)
229
	{
230
		return in_array($operation, array('OPTIMIZE', 'REPAIR', 'CHECK'));
231
	}
232
233
	/**
234
	* Only allow tables using MyISAM, InnoDB or Archive storage engines
235
	*
236
	* @param string $engine The name of the engine
237
	* @return bool True if valid engine, false otherwise
238
	* @access public
239
	*/
240
	public function is_valid_engine($engine)
241
	{
242
		return in_array(strtolower($engine), array('myisam', 'innodb', 'archive'));
243
	}
244
245
	/**
246
	* Is the storage engine InnoDB
247
	*
248
	* @param string $engine The name of the engine
249
	* @return bool True if InnoDB engine, false otherwise
250
	* @access public
251
	*/
252
	public function is_innodb($engine)
253
	{
254
		return strtolower($engine) == 'innodb';
255
	}
256
257
	/**
258
	* Set disable board config state
259
	*
260
	* @param int  $disable The users option to disable the board during run time
261
	* @param bool $switch  True to disable board, false to enable board
262
	* @return null
263
	* @access public
264
	*/
265
	public function disable_board($disable, $switch = true)
266
	{
267
		if ($disable)
268
		{
269
			$this->config->set('board_disable', (int) $switch);
270
		}
271
	}
272
273
	/**
274
	* Display file size in the proper units
275
	*
276
	* @param int $size Number representing bytes
277
	* @return string $size with the correct units symbol appended
278
	* @access public
279
	*/
280
	public function file_size($size)
281
	{
282
		$file_size_units = array(' B', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB');
283
		return (intval($size)) ? round($size / pow(1024, ($i = floor(log($size) / log(1024)))), 1) . $file_size_units[(int) $i] : '0 B';
284
	}
285
286
	/**
287
	* Extend execution limits to mitigate timeouts
288
	*
289
	* @return null
290
	* @access protected
291
	*/
292
	protected function extend_execution_limits()
293
	{
294
		// Disable safe mode to allow set_time_limit to work
295
		if ($this->php_ini->get_bool('safe_mode'))
296
		{
297
			@ini_set('safe_mode', 'Off');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
298
		}
299
300
		// Extend or disable script execution timeout (copied from acp_database.php)
301
		@set_time_limit(1200);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
302
		@set_time_limit(0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
303
	}
304
}
305