Issues (65)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/DB/ChangeTracker.php (1 issue)

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
use WordPress\Tabulate\DB\Database;
12
use WordPress\Tabulate\DB\Table;
13
use WordPress\Tabulate\DB\Record;
14
15
/**
16
 * The Change Tracker keeps a log of every data modification made through Tabulate.
17
 */
18
class ChangeTracker {
19
20
	/**
21
	 * The global wpdb object.
22
	 *
23
	 * @var \wpdb
24
	 */
25
	protected $wpdb;
26
27
	/**
28
	 * The ID of the currently-open changeset.
29
	 *
30
	 * @var integer
31
	 */
32
	private static $current_changeset_id = false;
33
34
	/**
35
	 * The user comment on the currently-open changeset.
36
	 *
37
	 * @var string
38
	 */
39
	private $current_changeset_comment = null;
40
41
	/**
42
	 * The record prior to modification.
43
	 *
44
	 * @var \WordPress\Tabulate\DB\Record|false
45
	 */
46
	private $old_record = false;
47
48
	/**
49
	 * Whether the changeset should be closed after the first after_save() call.
50
	 *
51
	 * @var boolean
52
	 */
53
	private static $keep_changeset_open = false;
54
55
	/**
56
	 * Create a new change tracker.
57
	 *
58
	 * @param \wpdb  $wpdb The global wpdb object.
59
	 * @param string $comment The user's comment about the change.
60
	 */
61
	public function __construct( $wpdb, $comment = null ) {
62
		$this->wpdb = $wpdb;
63
		$this->current_changeset_comment = $comment;
64
	}
65
66
	/**
67
	 * When destroying a ChangeTracker object, close the current changeset
68
	 * unless it has specifically been requested to be kept open.
69
	 */
70
	public function __destruct() {
71
		if ( ! self::$keep_changeset_open ) {
72
			$this->close_changeset();
73
		}
74
	}
75
76
	/**
77
	 * Open a new changeset. If one is already open, this does nothing.
78
	 *
79
	 * @global \WP_User $current_user
80
	 * @param string  $comment The user's comment on the changeset.
81
	 * @param boolean $keep_open Whether the changeset should be kept open (and manually closed) after after_save() is called.
82
	 * @throws Exception If the changeset row could not be saved.
83
	 */
84
	public function open_changeset( $comment, $keep_open = null ) {
85
		global $current_user;
86
		if ( ! is_null( $keep_open ) ) {
87
			self::$keep_changeset_open = $keep_open;
88
		}
89
		if ( ! self::$current_changeset_id ) {
90
			$data = array(
91
				'date_and_time' => date( 'Y-m-d H:i:s' ),
92
				'user_id' => $current_user->ID,
93
				'comment' => $comment,
94
			);
95
			$ret = $this->wpdb->insert( self::changesets_name(), $data );
96
			if ( false === $ret ) {
97
				throw new Exception( $this->wpdb->last_error . ' -- Unable to open changeset' );
98
			}
99
			self::$current_changeset_id = $this->wpdb->insert_id;
100
		}
101
	}
102
103
	/**
104
	 * Close the current changeset.
105
	 *
106
	 * @return void
107
	 */
108
	public function close_changeset() {
109
		self::$current_changeset_id = false;
0 ignored issues
show
Documentation Bug introduced by
The property $current_changeset_id was declared of type integer, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
110
		$this->current_changeset_comment = null;
111
	}
112
113
	/**
114
	 * This method is called prior to a record being saved, and will open a new
115
	 * changeset if required, and save the old record for later use.
116
	 *
117
	 * @param Table  $table The table into which the record is being saved.
118
	 * @param string $pk_value The primary key of the record being saved. May be null.
119
	 * @return boolean
120
	 */
121
	public function before_save( Table $table, $pk_value ) {
122
		// Don't save changes to the changes tables.
123
		if ( in_array( $table->get_name(), $this->table_names(), true ) ) {
124
			return false;
125
		}
126
127
		// Open a changeset if required.
128
		$this->open_changeset( $this->current_changeset_comment );
129
130
		// Get the current (i.e. soon-to-be-old) data for later use.
131
		$this->old_record = $table->get_record( $pk_value );
132
	}
133
134
	/**
135
	 * This method is called after a record has been saved, and is responsible
136
	 * for creating the actual change-tracking rows in the database.
137
	 *
138
	 * @param Table  $table The table the record is being saved in.
139
	 * @param Record $new_record The record, after being saved.
140
	 * @return boolean
141
	 */
142
	public function after_save( Table $table, Record $new_record ) {
143
		// Don't save changes to the changes tables.
144
		if ( in_array( $table->get_name(), self::table_names(), true ) ) {
145
			return false;
146
		}
147
148
		// Save a change for each changed column.
149
		foreach ( $table->get_columns() as $column ) {
150
			$col_name = ( $column->is_foreign_key() ) ? $column->get_name() . Record::FKTITLE : $column->get_name();
151
			$old_val = ( is_callable( array( $this->old_record, $col_name ) ) ) ? $this->old_record->$col_name() : null;
152
			$new_val = $new_record->$col_name();
153
			if ( $new_val === $old_val ) {
154
				// Ignore unchanged columns.
155
				continue;
156
			}
157
			$data = array(
158
				'changeset_id' => self::$current_changeset_id,
159
				'change_type' => 'field',
160
				'table_name' => $table->get_name(),
161
				'column_name' => $column->get_name(),
162
				'record_ident' => $new_record->get_primary_key(),
163
			);
164
			// Daft workaround for https://core.trac.wordpress.org/ticket/15158 .
165
			if ( ! is_null( $old_val ) ) {
166
				$data['old_value'] = $old_val;
167
			}
168
			if ( ! is_null( $new_val ) ) {
169
				$data['new_value'] = $new_val;
170
			}
171
			// Save the change record.
172
			$this->wpdb->insert( $this->changes_name(), $data );
173
		}
174
175
		// Close the changeset if required.
176
		if ( ! self::$keep_changeset_open ) {
177
			$this->close_changeset();
178
		}
179
	}
180
181
	/**
182
	 * On plugin activation, create two new database tables.
183
	 *
184
	 * @param \wpdb $wpdb The global database object.
185
	 */
186
	public static function activate( \wpdb $wpdb ) {
187
		$db = new Database( $wpdb );
188 View Code Duplication
		if ( ! $db->get_table( self::changesets_name() ) ) {
189
			$sql = "CREATE TABLE IF NOT EXISTS `" . self::changesets_name() . "` (
190
			`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
191
			`date_and_time` DATETIME NOT NULL,
192
			`user_id` BIGINT(20) UNSIGNED NOT NULL,
193
			FOREIGN KEY (`user_id`) REFERENCES `{$wpdb->prefix}users` (`ID`),
194
			`comment` TEXT NULL DEFAULT NULL
195
			);";
196
			$wpdb->query( $sql );
197
		}
198 View Code Duplication
		if ( ! $db->get_table( self::changes_name() ) ) {
199
			$sql = "CREATE TABLE IF NOT EXISTS `" . self::changes_name() . "` (
200
			`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
201
			`changeset_id` INT(10) UNSIGNED NOT NULL,
202
			FOREIGN KEY (`changeset_id`) REFERENCES `" . self::changesets_name() . "` (`id`),
203
			`change_type` ENUM('field', 'file', 'foreign_key') NOT NULL DEFAULT 'field',
204
			`table_name` TEXT(65) NOT NULL,
205
			`record_ident` TEXT(65) NOT NULL,
206
			`column_name` TEXT(65) NOT NULL,
207
			`old_value` LONGTEXT NULL DEFAULT NULL,
208
			`new_value` LONGTEXT NULL DEFAULT NULL
209
			);";
210
			$wpdb->query( $sql );
211
		}
212
	}
213
214
	/**
215
	 * Get the name of the changesets table.
216
	 *
217
	 * @global \WordPress\Tabulate\DB\wpdb $wpdb
218
	 * @return string
219
	 */
220
	public static function changesets_name() {
221
		global $wpdb;
222
		return $wpdb->prefix . TABULATE_SLUG . '_changesets';
223
	}
224
225
	/**
226
	 * Get the name of the changes table.
227
	 *
228
	 * @global \WordPress\Tabulate\DB\wpdb $wpdb
229
	 * @return string
230
	 */
231
	public static function changes_name() {
232
		global $wpdb;
233
		return $wpdb->prefix . TABULATE_SLUG . '_changes';
234
	}
235
236
	/**
237
	 * Get a list of the names used by the change-tracking subsystem.
238
	 *
239
	 * @global wpdb $wpdb
240
	 * @return array|string
241
	 */
242
	public static function table_names() {
243
		return array( self::changesets_name(), self::changes_name() );
244
	}
245
}
246