Completed
Push — master ( de6a17...35dc2a )
by Sam
02:15
created

src/Controllers/TableController.php (4 issues)

Severity

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
	 */
28
	protected function get_table( $table_name ) {
29
		$db = new Database( $this->wpdb );
30
		$table = $db->get_table( $table_name );
31
		if ( ! $table ) {
32
			add_action( 'admin_notices', function( $table_name ) use ( $table_name ) {
33
				// Translators: Error message shown when the table can not be found.
34
				$err = __( 'Table "%s" not found.', 'tabulate' );
35
				echo "<div class='error'><p>" . sprintf( $err, $table_name ) . "</p></div>";
36
			} );
37
			$home = new HomeController( $this->wpdb );
38
			return $home->index();
39
		}
40
		return $table;
41
	}
42
43
	/**
44
	 * View and search a table's data.
45
	 *
46
	 * @param string[] $args The request arguments.
47
	 * @return string
48
	 */
49
	public function index( $args ) {
50
		$table = $this->get_table( $args['table'] );
51
		if ( ! $table instanceof Table ) {
52
			return $table;
53
		}
54
55
		// Pagination.
56
		$page_num = (isset( $args['p'] ) && is_numeric( $args['p'] ) ) ? abs( $args['p'] ) : 1;
57
		$table->set_current_page_num( $page_num );
58
		if ( isset( $args['psize'] ) ) {
59
			$table->set_records_per_page( $args['psize'] );
60
		}
61
62
		// Ordering.
63
		if ( isset( $args['order_by'] ) ) {
64
			$table->set_order_by( $args['order_by'] );
65
		}
66
		if ( isset( $args['order_dir'] ) ) {
67
			$table->set_order_dir( $args['order_dir'] );
68
		}
69
70
		// Filters.
71
		$filter_param = (isset( $args['filter'] )) ? $args['filter'] : array();
72
		$table->add_filters( $filter_param );
73
74
		// Give it all to the template.
75
		$template = new \WordPress\Tabulate\Template( 'table.html' );
76
		$template->controller = 'table';
77
		$template->table = $table;
0 ignored issues
show
The property table does not exist on object<WordPress\Tabulate\Template>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
78
		$template->columns = $table->get_columns();
79
		$template->sortable = true;
80
		$template->record = $table->get_default_record();
81
		$template->records = $table->get_records();
82
		return $template->render();
83
	}
84
85
	/**
86
	 * This action is for importing a single CSV file into a single database table.
87
	 * It guides the user through the four stages of importing:
88
	 * uploading, field matching, previewing, and doing the actual import.
89
	 * All of the actual work is done in [WebDB_File_CSV].
90
	 *
91
	 * 1. In the first stage, a CSV file is **uploaded**, validated, and moved to a temporary directory.
92
	 *    The file is then accessed from this location in the subsequent stages of importing,
93
	 *    and only deleted upon either successful import or the user cancelling the process.
94
	 *    (The 'id' parameter of this action is the identifier for the uploaded file.)
95
	 * 2. Once a valid CSV file has been uploaded,
96
	 *    its colums are presented to the user to be **matched** to those in the database table.
97
	 *    The columns from the database are presented first and the CSV columns are matched to these,
98
	 *    rather than vice versa,
99
	 *    because this way the user sees immediately what columns are available to be imported into.
100
	 * 3. The column matches are then used to produce a **preview** of what will be added to and/or changed in the database.
101
	 *    All columns from the database are shown (regardless of whether they were in the import) and all rows of the import.
102
	 *    If a column is not present in the import the database will (obviously) use the default value if there is one;
103
	 *    this will be shown in the preview.
104
	 * 4. When the user accepts the preview, the actual **import** of data is carried out.
105
	 *    Rows are saved to the database using the usual Table::save() method
106
	 *    and a message presented to the user to indicate successful completion.
107
	 *
108
	 * @param string[] $args The request parameters.
109
	 * @return string
110
	 */
111
	public function import( $args ) {
112
		$template = new \WordPress\Tabulate\Template( 'import.html' );
113
		// Set up the progress bar.
114
		$template->stages = array(
115
			'choose_file',
116
			'match_fields',
117
			'preview',
118
			'complete_import',
119
		);
120
		$template->stage = 'choose_file';
121
122
		// First make sure the user is allowed to import data into this table.
123
		$table = $this->get_table( $args['table'] );
124
		$template->record = $table->get_default_record();
125
		$template->action = 'import';
126
		$template->table = $table;
0 ignored issues
show
The property table does not exist on object<WordPress\Tabulate\Template>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
127
		$template->maxsize = size_format( wp_max_upload_size() );
128
		if ( ! Grants::current_user_can( Grants::IMPORT, $table->get_name() ) ) {
129
			$template->add_notice( 'error', 'You do not have permission to import data into this table.' );
130
			return $template->render();
131
		}
132
133
		/*
134
		 * Stage 1 of 4: Uploading.
135
		 */
136
		require_once ABSPATH . '/wp-admin/includes/file.php';
137
		$template->form_action = $table->get_url( 'import' );
138
		try {
139
			$hash = isset( $_GET['hash'] ) ? $_GET['hash'] : false;
140
			$uploaded = false;
141
			if ( isset( $_FILES['file'] ) ) {
142
				check_admin_referer( 'import-upload' );
143
				$uploaded = wp_handle_upload( $_FILES['file'], array(
144
					'action' => $template->action,
145
				) );
146
			}
147
			$csv_file = new CSV( $hash, $uploaded );
148
		} catch ( \Exception $e ) {
149
			$template->add_notice( 'error', $e->getMessage() );
150
			return $template->render();
151
		}
152
153
		/*
154
		 * Stage 2 of 4: Matching fields.
155
		 */
156
		if ( $csv_file->loaded() ) {
157
			$template->file = $csv_file;
158
			$template->stage = $template->stages[1];
159
			$template->form_action .= "&hash=" . $csv_file->hash;
160
		}
161
162
		/*
163
		 * Stage 3 of 4: Previewing.
164
		 */
165
		if ( $csv_file->loaded() && isset( $_POST['preview'] ) ) {
166
			check_admin_referer( 'import-preview' );
167
			$template->stage = $template->stages[2];
168
			$template->columns = serialize( $_POST['columns'] );
169
			$errors = array();
170
			// Make sure all required columns are selected.
171
			foreach ( $table->get_columns() as $col ) {
172
				// Handle missing columns separately; other column errors are
173
				// done in the CSV class. Missing columns don't matter if importing
174
				// existing records.
175
				$is_missing = empty( $_POST['columns'][ $col->get_name() ] );
176
				$pk = $table->get_pk_column();
177
				$pk_present = $pk && isset( $_POST['columns'][ $pk->get_name() ] );
178
				if ( ! $pk_present && $col->is_required() && $is_missing ) {
179
					$errors[] = array(
180
						'column_name' => '',
181
						'column_number' => '',
182
						'field_name' => $col->get_name(),
183
						'row_number' => 'N/A',
184
						'messages' => array( 'Column required, but not found in CSV' ),
185
					);
186
				}
187
			}
188
			$template->errors = empty( $errors ) ? $csv_file->match_fields( $table, wp_unslash( $_POST['columns'] ) ) : $errors;
189
		}
190
191
		/*
192
		 * Stage 4 of 4: Import.
193
		 */
194
		if ( $csv_file->loaded() && isset( $_POST['import'] ) ) {
195
			check_admin_referer( 'import-finish' );
196
			$template->stage = $template->stages[3];
197
			$this->wpdb->query( 'BEGIN' );
198
			$result = $csv_file->import_data( $table, unserialize( wp_unslash( $_POST['columns'] ) ) );
199
			$this->wpdb->query( 'COMMIT' );
200
			$template->add_notice( 'updated', 'Import complete; ' . $result . ' rows imported.' );
201
		}
202
203
		return $template->render();
204
	}
205
206
	/**
207
	 * A calendar for tables with a date column.
208
	 *
209
	 * @param string[] $args The request parameters.
210
	 * @return type
211
	 */
212
	public function calendar( $args ) {
213
		// @todo Validate args.
214
		$year_num = (isset( $args['year'] )) ? $args['year'] : date( 'Y' );
215
		$month_num = (isset( $args['month'] )) ? $args['month'] : date( 'm' );
216
217
		$template = new \WordPress\Tabulate\Template( 'calendar.html' );
218
		$table = $this->get_table( $args['table'] );
219
220
		$template->table = $table;
0 ignored issues
show
The property table does not exist on object<WordPress\Tabulate\Template>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
221
		$template->action = 'calendar';
222
		$template->record = $table->get_default_record();
223
224
		$factory = new \CalendR\Calendar();
225
		$template->weekdays = $factory->getWeek( new \DateTime( 'Monday this week' ) );
226
		$month = $factory->getMonth( new \DateTime( $year_num . '-' . $month_num . '-01' ) );
227
		$template->month = $month;
228
		$records = array();
229
		foreach ( $table->get_columns( 'date' ) as $date_col ) {
230
			$date_col_name = $date_col->get_name();
231
			// Filter to the just the requested month.
232
			$table->add_filter( $date_col_name, '>=', $month->getBegin()->format( 'Y-m-d' ) );
233
			$table->add_filter( $date_col_name, '<=', $month->getEnd()->format( 'Y-m-d' ) );
234
			foreach ( $table->get_records() as $rec ) {
235
				$date_val = $rec->$date_col_name();
236
				// Initialise the day's list of records.
237
				if ( ! isset( $records[ $date_val ] ) ) {
238
					$records[ $date_val ] = array();
239
				}
240
				// Add this record to the day's list.
241
				$records[ $date_val ][] = $rec;
242
			}
243
		}
244
		// $records is grouped by date, with each item in a single date being
245
		// an array with 'record' and 'column' keys.
246
		$template->records = $records;
247
248
		return $template->render();
249
	}
250
251
	/**
252
	 * Export the current table with the current filters applied.
253
	 * Filters are passed as request parameters, just as for the index action.
254
	 *
255
	 * @param string[] $args The request parameters.
256
	 * @return void
257
	 */
258
	public function export( $args ) {
259
		// Get table.
260
		$table = $this->get_table( $args['table'] );
261
262
		// Filter and export.
263
		$filter_param = ( isset( $args['filter'] )) ? $args['filter'] : array();
264
		$table->add_filters( $filter_param );
265
		$filename = $table->export();
266
267
		// Send CSV to client.
268
		$download_name = date( 'Y-m-d' ) . '_' . $table->get_name() . '.csv';
269
		header( 'Content-Encoding: UTF-8' );
270
		header( 'Content-type: text/csv; charset=UTF-8' );
271
		header( 'Content-Disposition: attachment; filename="' . $download_name . '"' );
272
		echo "\xEF\xBB\xBF";
273
		readfile( $filename );
274
		exit;
275
	}
276
277
	/**
278
	 * Download a CSV of given titles that could not be found in this table.
279
	 *
280
	 * @param string[] $args The request parameters.
281
	 */
282
	public function notfound( $args ) {
283
		// Get table.
284
		$table = $this->get_table( $args['table'] );
285
286
		// Get the values from the request, or give up.
287
		$filter_id = isset( $args['notfound'] ) ? $args['notfound'] : false;
288
		$values_string = isset( $args['filter'][ $filter_id ]['value'] ) ? $args['filter'][ $filter_id ]['value'] : false;
289
		if ( ! $table instanceof Table || ! $values_string ) {
290
			return;
291
		}
292
		$values = Util::split_newline( $values_string );
293
294
		// Find all values that exist.
295
		$title_col = $table->get_title_column();
296
		$table->add_filter( $title_col, 'in', $values_string );
297
298
		// And remove them from the list of supplied values.
299
		$recs = $table->get_records( false );
300
		foreach ( $recs as $rec ) {
301
			$key = array_search( $rec->get_title(), $values );
302
			if ( false !== $key ) {
303
				unset( $values[ $key ] );
304
			}
305
		}
306
307
		$download_name = date( 'Y-m-d' ) . '_' . $table->get_name() . '_not_found.csv';
308
		header( 'Content-Encoding: UTF-8' );
309
		header( 'Content-type: text/csv; charset=UTF-8' );
310
		header( 'Content-Disposition: attachment; filename="' . $download_name . '"' );
311
		echo "\xEF\xBB\xBF";
312
		echo $title_col->get_title() . "\n" . join( "\n", $values );
313
		exit;
314
	}
315
316
	/**
317
	 * Display a horizontal timeline of any table with a date field.
318
	 *
319
	 * @param string[] $args Request arguments.
320
	 * @return string
321
	 */
322
	public function timeline( $args ) {
323
		$table = $this->get_table( $args['table'] );
324
		$template = new \WordPress\Tabulate\Template( 'timeline.html' );
325
		$template->action = 'timeline';
326
		$template->table = $table;
0 ignored issues
show
The property table does not exist on object<WordPress\Tabulate\Template>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
327
		$start_date_arg = (isset( $args['start_date'] )) ? $args['start_date'] : date( 'Y-m-d' );
328
		$end_date_arg = (isset( $args['end_date'] )) ? $args['end_date'] : date( 'Y-m-d' );
329
		$start_date = new \DateTime( $start_date_arg );
330
		$end_date = new \DateTime( $end_date_arg );
331
		if ( $start_date->diff( $end_date, true )->d < 7 ) {
332
			// Add two weeks to the end date.
333
			$end_date->add( new \DateInterval( 'P14D' ) );
334
		}
335
		$date_period = new \DatePeriod( $start_date, new \DateInterval( 'P1D' ), $end_date );
336
		$template->start_date = $start_date->format( 'Y-m-d' );
337
		$template->end_date = $end_date->format( 'Y-m-d' );
338
		$template->date_period = $date_period;
339
		$data = array();
340
		foreach ( $table->get_records( false ) as $record ) {
341
			if ( ! isset( $data[ $record->get_title() ] ) ) {
342
				$data[ $record->get_title() ] = array();
343
			}
344
			$data[ $record->get_title() ][] = $record;
345
		}
346
		$template->data = $data;
347
		return $template->render();
348
	}
349
}
350