Issues (234)

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.

includes/admin/updater.php (6 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
 * Add-ons Updater.
4
 *
5
 * @package SimpleCalendar\Admin
6
 */
7
namespace SimpleCalendar\Admin;
8
9
/**
10
 * Add-ons updater.
11
 *
12
 * @since 3.0.0
13
 */
14
class Updater {
15
16
	/**
17
	 * API Url.
18
	 *
19
	 * @access private
20
	 * @var string
21
	 */
22
	private $api_url = '';
23
24
	/**
25
	 * API data.
26
	 *
27
	 * @access private
28
	 * @var array|null
29
	 */
30
	private $api_data = array();
31
32
	/**
33
	 * Name.
34
	 *
35
	 * @access private
36
	 * @var string
37
	 */
38
	private $name = '';
39
40
	/**
41
	 * Slug.
42
	 *
43
	 * @access private
44
	 * @var string
45
	 */
46
	private $slug = '';
47
48
	/**
49
	 * Version.
50
	 *
51
	 * @access private
52
	 * @var string
53
	 */
54
	private $version = '';
55
56
	/**
57
	 * Constructor.
58
	 *
59
	 * @since 3.0.0
60
	 *
61
	 * @param string $_api_url     The URL pointing to the custom API endpoint.
62
	 * @param string $_plugin_file Path to the plugin file.
63
	 * @param array  $_api_data    Optional data to send with API calls.
64
	 */
65
	public function __construct( $_api_url, $_plugin_file, $_api_data = null ) {
66
67
		$this->api_url  = trailingslashit( $_api_url );
68
		$this->api_data = $_api_data;
69
		$this->name     = plugin_basename( $_plugin_file );
70
		$this->slug     = basename( $_plugin_file, '.php' );
71
		$this->version  = $_api_data['version'];
72
73
		// Set up hooks.
74
		$this->init();
75
		add_action( 'admin_init', array( $this, 'show_changelog' ) );
76
	}
77
78
	/**
79
	 * Hook into WordPress update process.
80
	 *
81
	 * @since  3.0.0
82
	 *
83
	 * @return void
84
	 */
85
	public function init() {
86
		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
87
		add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
88
		remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10 );
89
		add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
90
	}
91
92
	/**
93
	 * Check for Updates at the defined API endpoint and modify the update array.
94
	 *
95
	 * This function dives into the update API just when WordPress creates its update array,
96
	 * then adds a custom API call and injects the custom plugin data retrieved from the API.
97
	 * It is reassembled from parts of the native WordPress plugin update code.
98
	 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
99
	 *
100
	 * @since  3.0.0
101
	 *
102
	 * @param  array $_transient_data Update array build by WordPress.
103
	 *
104
	 * @return array|\stdClass Modified update array with custom plugin data.
105
	 */
106
	public function check_update( $_transient_data ) {
107
108
		global $pagenow;
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...
109
110
		if ( ! is_object( $_transient_data ) ) {
111
			$_transient_data = new \stdClass();
112
		}
113
114
		if ( 'plugins.php' == $pagenow && is_multisite() ) {
115
			return $_transient_data;
116
		}
117
118
		if ( empty( $_transient_data->response ) || empty( $_transient_data->response[ $this->name ] ) ) {
119
120
			$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug ) );
121
122
			if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) {
123
124
				if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
125
					$_transient_data->response[ $this->name ] = $version_info;
126
				}
127
128
				$_transient_data->last_checked = time();
129
				$_transient_data->checked[ $this->name ] = $this->version;
130
			}
131
132
		}
133
134
		return $_transient_data;
135
	}
136
137
	/**
138
	 * Show update notification row.
139
	 *
140
	 * Needed for multisite subsites, because WordPress won't tell otherwise.
141
	 *
142
	 * @since 3.0.0
143
	 *
144
	 * @param string $file
145
	 * @param array  $plugin
146
	 */
147
	public function show_update_notification( $file, $plugin ) {
0 ignored issues
show
The parameter $plugin 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...
148
149
		if ( ! current_user_can( 'update_plugins' ) ) {
150
			return;
151
		}
152
153
		if ( ! is_multisite() ) {
154
			return;
155
		}
156
157
		if ( $this->name != $file ) {
158
			return;
159
		}
160
161
		// Remove our filter on the site transient
162
		remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
163
164
		$update_cache = get_site_transient( 'update_plugins' );
165
166
		if ( ! is_object( $update_cache ) || empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
167
168
			$cache_key    = md5( 'edd_plugin_' .sanitize_key( $this->name ) . '_version_info' );
169
			$version_info = get_transient( $cache_key );
170
171
			if ( false === $version_info ) {
172
173
				$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug ) );
174
175
				set_transient( $cache_key, $version_info, 3600 );
176
			}
177
178
			if ( ! is_object( $version_info ) ) {
179
				return;
180
			}
181
182
			if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
183
				$update_cache->response[ $this->name ] = $version_info;
184
			}
185
186
			$update_cache->last_checked = time();
187
			$update_cache->checked[ $this->name ] = $this->version;
188
189
			set_site_transient( 'update_plugins', $update_cache );
190
191
		} else {
192
193
			$version_info = $update_cache->response[ $this->name ];
194
195
		}
196
197
		// Restore our filter
198
		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
199
200
		if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
201
202
			// build a plugin list row, with update notification
203
			$wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
204
			echo '<tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange"><div class="update-message">';
205
206
			$changelog_link = self_admin_url( 'index.php?edd_sl_action=view_plugin_changelog&plugin=' . $this->name . '&slug=' . $this->slug . '&TB_iframe=true&width=772&height=911' );
207
208
			if ( empty( $version_info->download_link ) ) {
209
				printf(
210
					__( 'There is a new version of %1$s available. <a target="_blank" class="thickbox" href="%2$s">View version %3$s details</a>.', 'google-calendar-events' ),
211
					esc_html( $version_info->name ),
212
					esc_url( $changelog_link ),
213
					esc_html( $version_info->new_version )
214
				);
215
			} else {
216
				printf(
217
					__( 'There is a new version of %1$s available. <a target="_blank" class="thickbox" href="%2$s">View version %3$s details</a> or <a href="%4$s">update now</a>.', 'google-calendar-events' ),
218
					esc_html( $version_info->name ),
219
					esc_url( $changelog_link ),
220
					esc_html( $version_info->new_version ),
221
					esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) )
222
				);
223
			}
224
225
			echo '</div></td></tr>';
226
		}
227
	}
228
229
230
	/**
231
	 * Updates information on the "View version x.x details" page with custom data.
232
	 *
233
	 * @since  3.0.0
234
	 *
235
	 * @param  mixed  $_data
236
	 * @param  string $_action
237
	 * @param  object $_args
238
	 *
239
	 * @return object
240
	 */
241
	public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
242
243
		if ( 'plugin_information' != $_action ) {
244
			return $_data;
245
		}
246
247
		if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
248
			return $_data;
249
		}
250
251
		$to_send = array(
252
			'slug'   => $this->slug,
253
			'is_ssl' => is_ssl(),
254
			'fields' => array(
255
				'banners' => false, // These will be supported soon hopefully
256
				'reviews' => false
257
			),
258
		);
259
260
		$api_response = $this->api_request( 'plugin_information', $to_send );
261
262
		if ( false !== $api_response ) {
263
			$_data = $api_response;
264
		}
265
266
		return $_data;
267
	}
268
269
270
	/**
271
	 * Disable SSL verification in order to prevent download update failures
272
	 *
273
	 * @since  3.0.0
274
	 *
275
	 * @param  array  $args
276
	 * @param  string $url
277
	 * @return object|array $array
278
	 */
279
	public function http_request_args( $args, $url ) {
280
		// If it is an https request and we are performing a package download, disable ssl verification
281
		if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
282
			$args['sslverify'] = false;
283
		}
284
		return $args;
285
	}
286
287
	/**
288
	 * Calls the API and, if successful, returns the object delivered by the API.
289
	 *
290
	 * @since  3.0.0
291
	 *
292
	 * @param  string $_action The requested action.
293
	 * @param  array  $_data   Parameters for the API action.
294
	 * @return false|object
295
	 */
296
	private function api_request( $_action, $_data ) {
0 ignored issues
show
The parameter $_action 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...
297
298
		global $wp_version;
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...
299
300
		$data = array_merge( $this->api_data, $_data );
301
302
		if ( $data['slug'] != $this->slug ) {
303
			return;
304
		}
305
306
		if ( empty( $data['license'] ) ) {
307
			return;
308
		}
309
310
		if ( $this->api_url == home_url() ) {
311
			return false; // Don't allow a plugin to ping itself
312
		}
313
314
		$api_params = array(
315
			'edd_action' => 'get_version',
316
			'license'    => $data['license'],
317
			'item_name'  => isset( $data['item_name'] ) ? $data['item_name'] : false,
318
			'item_id'    => isset( $data['item_id'] ) ? $data['item_id'] : false,
319
			'slug'       => $data['slug'],
320
			'author'     => $data['author'],
321
			'url'        => home_url()
322
		);
323
324
		$request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );
325
326
		if ( ! is_wp_error( $request ) ) {
327
			$request = json_decode( wp_remote_retrieve_body( $request ) );
328
		}
329
330
		if ( $request && isset( $request->sections ) ) {
331
			$request->sections = maybe_unserialize( $request->sections );
332
		} else {
333
			$request = false;
334
		}
335
336
		return $request;
337
	}
338
339
	/**
340
	 * Show changelog.
341
	 *
342
	 * @since 3.0.0
343
	 */
344
	public function show_changelog() {
0 ignored issues
show
show_changelog uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
345
346
		if ( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
347
			return;
348
		}
349
350
		if ( empty( $_REQUEST['plugin'] ) ) {
351
			return;
352
		}
353
354
		if ( empty( $_REQUEST['slug'] ) ) {
355
			return;
356
		}
357
358
		if ( ! current_user_can( 'update_plugins' ) ) {
359
			wp_die( __( 'You do not have permission to install plugin updates', 'google-calendar-events' ), __( 'Error', 'google-calendar-events' ), array( 'response' => 403 ) );
360
		}
361
362
		$response = $this->api_request( 'plugin_latest_version', array( 'slug' => $_REQUEST['slug'] ) );
363
364
		if ( $response && isset( $response->sections['changelog'] ) ) {
365
			echo '<div style="background:#fff;padding:10px;">' . $response->sections['changelog'] . '</div>';
366
		}
367
368
		exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method show_changelog() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
369
	}
370
371
}
372