Completed
Push — master ( 7301c6...131523 )
by Sam
24:35 queued 17:33
created

src/Controllers/TableController.php (1 issue)

mismatching argument types.

Documentation Minor

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\Controllers;
10
11
use \WordPress\Tabulate\Util;
12
use \WordPress\Tabulate\DB\Grants;
13
use \WordPress\Tabulate\DB\Database;
14
use \WordPress\Tabulate\DB\Table;
15
use \WordPress\Tabulate\CSV;
16
17
/**
18
 * The table controller handles viewing, exporting, and importing table data.
19
 */
20
class TableController extends ControllerBase {
21
22
	/**
23
	 * Get a Table object for a given table, or an error message and the
24
	 * Tabulate overview page.
25
	 *
26
	 * @param string $table_name The name of the table to get.
27
	 * @return Table|string The table, or an HTML error message.
28
	 */
29
	protected function get_table( $table_name ) {
30
		$db = new Database( $this->wpdb );
31
		$db->set_filesystem( $this->filesystem );
32
		$table = $db->get_table( $table_name );
33
		if ( ! $table ) {
34
			add_action( 'admin_notices', function() use ( $table_name ) {
35
				// Translators: Error message shown when the table can not be found.
36
				$err = __( 'Table "%s" not found.', 'tabulate' );
37
				echo "<div class='error'><p>" . sprintf( $err, $table_name ) . "</p></div>";
38
			} );
39
			$home = new HomeController( $this->wpdb );
40
			return $home->index();
41
		}
42
		return $table;
43
	}
44
45
	/**
46
	 * View and search a table's data.
47
	 *
48
	 * @param string[] $args The request arguments.
49
	 * @return string
50
	 */
51
	public function index( $args ) {
52
		$table = $this->get_table( $args['table'] );
53
		if ( ! $table instanceof Table ) {
54
			return $table;
55
		}
56
57
		// Pagination.
58
		$page_num = (isset( $args['p'] ) && is_numeric( $args['p'] ) ) ? abs( $args['p'] ) : 1;
59
		$table->set_current_page_num( $page_num );
60
		if ( isset( $args['psize'] ) ) {
61
			$table->set_records_per_page( $args['psize'] );
62
		}
63
64
		// Ordering.
65
		if ( isset( $args['order_by'] ) ) {
66
			$table->set_order_by( $args['order_by'] );
67
		}
68
		if ( isset( $args['order_dir'] ) ) {
69
			$table->set_order_dir( $args['order_dir'] );
70
		}
71
72
		// Filters.
73
		$filter_param = (isset( $args['filter'] )) ? $args['filter'] : array();
74
		$table->add_filters( $filter_param );
75
76
		// Give it all to the template.
77
		$template = new \WordPress\Tabulate\Template( 'table.html' );
78
		$template->controller = 'table';
79
		$template->table = $table;
80
		$template->columns = $table->get_columns();
81
		$template->sortable = true;
82
		$template->record = $table->get_default_record();
83
		$template->records = $table->get_records();
84
		return $template->render();
85
	}
86
87
	/**
88
	 * This action is for importing a single CSV file into a single database table.
89
	 * It guides the user through the four stages of importing:
90
	 * uploading, field matching, previewing, and doing the actual import.
91
	 * All of the actual work is done in [WebDB_File_CSV].
92
	 *
93
	 * 1. In the first stage, a CSV file is **uploaded**, validated, and moved to a temporary directory.
94
	 *    The file is then accessed from this location in the subsequent stages of importing,
95
	 *    and only deleted upon either successful import or the user cancelling the process.
96
	 *    (The 'id' parameter of this action is the identifier for the uploaded file.)
97
	 * 2. Once a valid CSV file has been uploaded,
98
	 *    its colums are presented to the user to be **matched** to those in the database table.
99
	 *    The columns from the database are presented first and the CSV columns are matched to these,
100
	 *    rather than vice versa,
101
	 *    because this way the user sees immediately what columns are available to be imported into.
102
	 * 3. The column matches are then used to produce a **preview** of what will be added to and/or changed in the database.
103
	 *    All columns from the database are shown (regardless of whether they were in the import) and all rows of the import.
104
	 *    If a column is not present in the import the database will (obviously) use the default value if there is one;
105
	 *    this will be shown in the preview.
106
	 * 4. When the user accepts the preview, the actual **import** of data is carried out.
107
	 *    Rows are saved to the database using the usual Table::save() method
108
	 *    and a message presented to the user to indicate successful completion.
109
	 *
110
	 * @param string[] $args The request parameters.
111
	 * @return string
112
	 */
113
	public function import( $args ) {
114
		$template = new \WordPress\Tabulate\Template( 'import.html' );
115
		// Set up the progress bar.
116
		$template->stages = array(
117
			'choose_file',
118
			'match_fields',
119
			'preview',
120
			'complete_import',
121
		);
122
		$template->stage = 'choose_file';
123
124
		// First make sure the user is allowed to import data into this table.
125
		$table = $this->get_table( $args['table'] );
126
		$template->record = $table->get_default_record();
127
		$template->action = 'import';
128
		$template->table = $table;
129
		$template->maxsize = size_format( wp_max_upload_size() );
130
		if ( ! Grants::current_user_can( Grants::IMPORT, $table->get_name() ) ) {
131
			$template->add_notice( 'error', 'You do not have permission to import data into this table.' );
132
			return $template->render();
133
		}
134
135
		/*
136
		 * Stage 1 of 4: Uploading.
137
		 */
138
		require_once ABSPATH . '/wp-admin/includes/file.php';
139
		$template->form_action = $table->get_url( 'import' );
140
		try {
141
			$hash = isset( $_GET['hash'] ) ? $_GET['hash'] : false;
142
			$uploaded = false;
143
			if ( isset( $_FILES['file'] ) ) {
144
				check_admin_referer( 'import-upload' );
145
				$uploaded = wp_handle_upload( $_FILES['file'], array(
146
					'action' => $template->action,
147
				) );
148
			}
149
			$csv_file = new CSV( $this->filesystem, $hash, $uploaded );
150
		} catch ( \Exception $e ) {
151
			$template->add_notice( 'error', $e->getMessage() );
152
			return $template->render();
153
		}
154
155
		/*
156
		 * Stage 2 of 4: Matching fields.
157
		 */
158
		if ( $csv_file->loaded() ) {
159
			$template->file = $csv_file;
160
			$template->stage = $template->stages[1];
161
			$template->form_action .= "&hash=" . $csv_file->hash;
162
		}
163
164
		/*
165
		 * Stage 3 of 4: Previewing.
166
		 */
167
		if ( $csv_file->loaded() && isset( $_POST['preview'] ) ) {
168
			check_admin_referer( 'import-preview' );
169
			$template->stage = $template->stages[2];
170
			$template->columns = wp_json_encode( $_POST['columns'] );
171
			$errors = array();
172
			// Make sure all required columns are selected.
173
			foreach ( $table->get_columns() as $col ) {
174
				// Handle missing columns separately; other column errors are
175
				// done in the CSV class. Missing columns don't matter if importing
176
				// existing records.
177
				$is_missing = empty( $_POST['columns'][ $col->get_name() ] );
178
				$pk = $table->get_pk_column();
179
				$pk_present = $pk && isset( $_POST['columns'][ $pk->get_name() ] );
180
				if ( ! $pk_present && $col->is_required() && $is_missing ) {
181
					$errors[] = array(
182
						'column_name' => '',
183
						'column_number' => '',
184
						'field_name' => $col->get_name(),
185
						'row_number' => 'N/A',
186
						'messages' => array( 'Column required, but not found in CSV' ),
187
					);
188
				}
189
			}
190
			$template->errors = empty( $errors ) ? $csv_file->match_fields( $table, wp_unslash( $_POST['columns'] ) ) : $errors;
191
		}
192
193
		/*
194
		 * Stage 4 of 4: Import.
195
		 */
196
		if ( $csv_file->loaded() && isset( $_POST['import'] ) ) {
197
			check_admin_referer( 'import-finish' );
198
			$template->stage = $template->stages[3];
199
			$this->wpdb->query( 'BEGIN' );
200
			$column_map = json_decode( wp_unslash( $_POST['columns'] ), true );
201
			$result = $csv_file->import_data( $table, $column_map );
202
			$this->wpdb->query( 'COMMIT' );
203
			$template->add_notice( 'updated', 'Import complete; ' . $result . ' rows imported.' );
204
		}
205
206
		return $template->render();
207
	}
208
209
	/**
210
	 * A calendar for tables with a date column.
211
	 *
212
	 * @param string[] $args The request parameters.
213
	 * @return string The calendar HTML.
214
	 */
215
	public function calendar( $args ) {
216
		// @todo Validate args.
217
		$year_num = (isset( $args['year'] )) ? $args['year'] : date( 'Y' );
218
		$month_num = (isset( $args['month'] )) ? $args['month'] : date( 'm' );
219
220
		$template = new \WordPress\Tabulate\Template( 'calendar.html' );
221
		$table = $this->get_table( $args['table'] );
222
223
		$template->table = $table;
224
		$template->action = 'calendar';
225
		$template->record = $table->get_default_record();
226
227
		$factory = new \CalendR\Calendar();
228
		$template->weekdays = $factory->getWeek( new \DateTime( 'Monday this week' ) );
229
		$month = $factory->getMonth( new \DateTime( $year_num . '-' . $month_num . '-01' ) );
230
		$template->month = $month;
231
		$records = array();
232
		foreach ( $table->get_columns( 'date' ) as $date_col ) {
233
			$date_col_name = $date_col->get_name();
234
			// Filter to the just the requested month.
235
			$table->add_filter( $date_col_name, '>=', $month->getBegin()->format( 'Y-m-d' ) );
236
			$table->add_filter( $date_col_name, '<=', $month->getEnd()->format( 'Y-m-d' ) );
237
			foreach ( $table->get_records() as $rec ) {
238
				$date_val = $rec->$date_col_name();
239
				// Initialise the day's list of records.
240
				if ( ! isset( $records[ $date_val ] ) ) {
241
					$records[ $date_val ] = array();
242
				}
243
				// Add this record to the day's list.
244
				$records[ $date_val ][] = $rec;
245
			}
246
		}
247
		// $records is grouped by date, with each item in a single date being
248
		// an array with 'record' and 'column' keys.
249
		$template->records = $records;
250
251
		return $template->render();
252
	}
253
254
	/**
255
	 * Export the current table with the current filters applied.
256
	 * Filters are passed as request parameters, just as for the index action.
257
	 *
258
	 * @param string[] $args The request parameters.
259
	 * @return void
260
	 */
261
	public function export( $args ) {
262
		// Get table.
263
		$table = $this->get_table( $args['table'] );
264
265
		// Filter and export.
266
		$filter_param = ( isset( $args['filter'] )) ? $args['filter'] : array();
267
		$table->add_filters( $filter_param );
0 ignored issues
show
$filter_param is of type string|array, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
268
		$filename = $table->export();
269
270
		// Send CSV to client.
271
		$download_name = date( 'Y-m-d' ) . '_' . $table->get_name() . '.csv';
272
		header( 'Content-Encoding: UTF-8' );
273
		header( 'Content-type: text/csv; charset=UTF-8' );
274
		header( 'Content-Disposition: attachment; filename="' . $download_name . '"' );
275
		echo "\xEF\xBB\xBF";
276
		echo $table->get_database()->get_filesystem()->get_contents( $filename );
277
		exit;
278
	}
279
280
	/**
281
	 * Download a CSV of given titles that could not be found in this table.
282
	 *
283
	 * @param string[] $args The request parameters.
284
	 */
285
	public function notfound( $args ) {
286
		// Get table.
287
		$table = $this->get_table( $args['table'] );
288
289
		// Get the values from the request, or give up.
290
		$filter_id = isset( $args['notfound'] ) ? $args['notfound'] : false;
291
		$values_string = isset( $args['filter'][ $filter_id ]['value'] ) ? $args['filter'][ $filter_id ]['value'] : false;
292
		if ( ! $table instanceof Table || ! $values_string ) {
293
			return;
294
		}
295
		$values = Util::split_newline( $values_string );
296
297
		// Find all values that exist.
298
		$title_col = $table->get_title_column();
299
		$table->add_filter( $title_col, 'in', $values_string );
300
301
		// And remove them from the list of supplied values.
302
		$recs = $table->get_records( false );
303
		foreach ( $recs as $rec ) {
304
			$key = array_search( $rec->get_title(), $values, true );
305
			if ( false !== $key ) {
306
				unset( $values[ $key ] );
307
			}
308
		}
309
310
		$download_name = date( 'Y-m-d' ) . '_' . $table->get_name() . '_not_found.csv';
311
		header( 'Content-Encoding: UTF-8' );
312
		header( 'Content-type: text/csv; charset=UTF-8' );
313
		header( 'Content-Disposition: attachment; filename="' . $download_name . '"' );
314
		echo "\xEF\xBB\xBF";
315
		echo $title_col->get_title() . "\n" . join( "\n", $values );
316
		exit;
317
	}
318
319
	/**
320
	 * Display a horizontal timeline of any table with a date field.
321
	 *
322
	 * @param string[] $args Request arguments.
323
	 * @return string
324
	 */
325
	public function timeline( $args ) {
326
		$table = $this->get_table( $args['table'] );
327
		$template = new \WordPress\Tabulate\Template( 'timeline.html' );
328
		$template->action = 'timeline';
329
		$template->table = $table;
330
		$start_date_arg = (isset( $args['start_date'] )) ? $args['start_date'] : date( 'Y-m-d' );
331
		$end_date_arg = (isset( $args['end_date'] )) ? $args['end_date'] : date( 'Y-m-d' );
332
		$start_date = new \DateTime( $start_date_arg );
333
		$end_date = new \DateTime( $end_date_arg );
334
		if ( $start_date->diff( $end_date, true )->d < 7 ) {
335
			// Add two weeks to the end date.
336
			$end_date->add( new \DateInterval( 'P14D' ) );
337
		}
338
		$date_period = new \DatePeriod( $start_date, new \DateInterval( 'P1D' ), $end_date );
339
		$template->start_date = $start_date->format( 'Y-m-d' );
340
		$template->end_date = $end_date->format( 'Y-m-d' );
341
		$template->date_period = $date_period;
342
		$data = array();
343
		foreach ( $table->get_records( false ) as $record ) {
344
			if ( ! isset( $data[ $record->get_title() ] ) ) {
345
				$data[ $record->get_title() ] = array();
346
			}
347
			$data[ $record->get_title() ][] = $record;
348
		}
349
		$template->data = $data;
350
		return $template->render();
351
	}
352
}
353