Completed
Push — master ( 6cab57...287bd7 )
by Sam
02:34
created

src/DB/Database.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * This file contains only a single class.
4
 *
5
 * @file
6
 * @package Tabulate
7
 */
8
9
namespace WordPress\Tabulate\DB;
10
11
/**
12
 * The database class represents the entire MySQL database that WordPress uses.
13
 */
14
class Database {
15
16
	/**
17
	 * The global wpdb object.
18
	 *
19
	 * @var \wpdb
20
	 */
21
	protected $wpdb;
22
23
	/**
24
	 * A list of all table names.
25
	 *
26
	 * @var string[]
27
	 */
28
	protected $table_names;
29
30
	/**
31
	 * The filesystem.
32
	 *
33
	 * @var \WP_Filesystem_Base
34
	 */
35
	protected $filesystem;
36
37
	/**
38
	 * The list of all tables that the user can read.
39
	 *
40
	 * @var Table[]
41
	 */
42
	protected $tables;
43
44
	/**
45
	 * Create a new Database object based on the given wpdb object.
46
	 *
47
	 * @param \wpdb $wpdb The global wpdb object.
48
	 */
49
	public function __construct( $wpdb ) {
50
		$this->wpdb = $wpdb;
51
	}
52
53
	/**
54
	 * Set the filesystem.
55
	 *
56
	 * @param \WP_Filesystem_Base $filesystem The filesystem object.
57
	 */
58
	public function set_filesystem( \WP_Filesystem_Base $filesystem ) {
59
		$this->filesystem = $filesystem;
60
	}
61
62
	/**
63
	 * Get the filesystem.
64
	 *
65
	 * @return \WP_Filesystem_Base
66
	 */
67
	public function get_filesystem() {
68
		return $this->filesystem;
69
	}
70
71
	/**
72
	 * Get the global wpdb object.
73
	 *
74
	 * @return \wpdb
75
	 */
76
	public function get_wpdb() {
77
		return $this->wpdb;
78
	}
79
80
	/**
81
	 * Get a list of tables and views that the current user can read.
82
	 *
83
	 * @return string[] The table names.
84
	 */
85
	public function get_table_names() {
86
		if ( ! $this->table_names ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->table_names of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
87
			$this->table_names = array();
88
			foreach ( $this->wpdb->get_col( 'SHOW TABLES' ) as $table_name ) {
89
				if ( Grants::current_user_can( Grants::READ, $table_name ) ) {
90
					$this->table_names[ $table_name ] = $table_name;
91
				}
92
			}
93
		}
94
		return $this->table_names;
95
	}
96
97
	/**
98
	 * Get a table from the database.
99
	 *
100
	 * @param string $name The name of the desired table.
101
	 * @return \WordPress\Tabulate\DB\Table|false The table, or false if it's not available.
102
	 */
103
	public function get_table( $name ) {
104
		if ( ! in_array( $name, $this->get_table_names(), true ) ) {
105
			return false;
106
		}
107
		if ( ! isset( $this->tables[ $name ] ) ) {
108
			$this->tables[ $name ] = new Table( $this, $name );
109
		}
110
		return $this->tables[ $name ];
111
	}
112
113
	/**
114
	 * Forget all table information, forcing it to be re-read from the database
115
	 * when next required. Used after schema changes.
116
	 */
117
	public function reset() {
118
		$this->table_names = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type array<integer,string> of property $table_names.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
119
		$this->tables = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type array<integer,object<Wor...ess\Tabulate\DB\Table>> of property $tables.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
120
	}
121
122
	/**
123
	 * Get all tables in this database.
124
	 *
125
	 * @param boolean $exclude_views Whether to exclude database views from the returned list.
126
	 * @return Table[] An array of all Tables.
127
	 */
128
	public function get_tables( $exclude_views = true ) {
129
		$out = array();
130
		foreach ( $this->get_table_names() as $name ) {
131
			$table = $this->get_table( $name );
132
			// If this table is not available, skip it.
133
			if ( ! $table ) {
134
				continue;
135
			}
136
			if ( $exclude_views && $table->is_view() ) {
137
				continue;
138
			}
139
			$out[ $table->get_name() ] = $table;
140
		}
141
		return $out;
142
	}
143
144
	/**
145
	 * Get all views in this database.
146
	 *
147
	 * @return Table|array An array of all Tables that are views.
148
	 */
149
	public function get_views() {
150
		$out = array();
151
		foreach ( $this->get_tables( false ) as $table ) {
152
			if ( $table->is_view() ) {
153
				$out[ $table->get_name() ] = $table;
154
			}
155
		}
156
		return $out;
157
	}
158
159
	/**
160
	 * Create a new table.
161
	 *
162
	 * @param string $name The name of the new table.
163
	 * @param string $comment The table comment.
164
	 * @throws Exception If the current user cannot 'promote_users'.
165
	 */
166
	public function create_table( $name, $comment = '' ) {
167
		if ( ! current_user_can( 'promote_users' ) ) {
168
			throw new Exception( 'Only administrators are allowed to create tables' );
169
		}
170
		$sql = "CREATE TABLE IF NOT EXISTS `$name` ( "
171
			. " `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY "
172
			. ") ENGINE=InnoDB, COMMENT='$comment';";
173
		$this->query( $sql );
174
		$this->reset();
175
		return $this->get_table( $name );
176
	}
177
178
	/**
179
	 * A wrapper around wpdb::prepare() and wpdb::query()
180
	 * that also checks wpdb::$last_error and throws up on occasion of badness.
181
	 *
182
	 * @param string   $sql The SQL statement to execute.
183
	 * @param string[] $params Parameters to pass to wpdb::prepare().
184
	 * @param string   $error_message What to tell the user if this query fails.
185
	 * @throws Exception The exception message is taken from wpdb::$last_error and if WP_DEBUG is set will also include the erroneous SQL.
186
	 */
187
	public function query( $sql, $params = null, $error_message = null ) {
188
		if ( $params ) {
189
			$sql = $this->get_wpdb()->prepare( $sql, $params );
190
		}
191
		$this->get_wpdb()->query( $sql );
192
		if ( ! empty( $this->get_wpdb()->last_error ) ) {
193
			$msg = $error_message . ': ' . $this->get_wpdb()->last_error;
194
			if ( WP_DEBUG ) {
195
				$msg .= " <code>$sql</code>";
196
			}
197
			throw new Exception( $msg );
198
		}
199
	}
200
201
	/**
202
	 * Get the name of the directory to which MySQL will write temporary export files.
203
	 * This is either the value of the 'secure_file_priv' server variable,
204
	 * or WordPress's normal temporary directory as returned by get_temp_dir().
205
	 * Always has a trailing slash.
206
	 *
207
	 * @return string Full path of the directory.
208
	 * @throws Exception If the directory is not writable.
209
	 */
210
	public function get_tmp_dir() {
211
		$query = "SHOW VARIABLES LIKE 'secure_file_priv';";
212
		$db_dir = $this->get_wpdb()->get_var( $query, 1 );
213
		$dir = empty( $db_dir ) ? get_temp_dir() : $db_dir;
214
		$out = rtrim( $dir, '/' ) . '/';
215
		if ( ! $this->get_filesystem()->is_writable( $out ) ) {
216
			throw new Exception( "Unable to write to temporary directory $out" );
217
		}
218
		return $out;
219
	}
220
}
221