GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 74a577...62d274 )
by Brad
02:30
created

FS_Plugin_Info_Dialog::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 1
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 19 and the first side effect is on line 10.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
	/**
3
	 * @package     Freemius
4
	 * @copyright   Copyright (c) 2015, Freemius, Inc.
5
	 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
6
	 * @since       1.0.6
7
	 */
8
9
	if ( ! defined( 'ABSPATH' ) ) {
10
		exit;
11
	}
12
13
	/**
14
	 * Class FS_Plugin_Info_Dialog
15
	 *
16
	 * @author Vova Feldman (@svovaf)
17
	 * @since  1.1.7
18
	 */
19
	class FS_Plugin_Info_Dialog {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
20
		/**
21
		 * @since 1.1.7
22
		 *
23
		 * @var FS_Logger
24
		 */
25
		private $_logger;
26
27
		/**
28
		 * @since 1.1.7
29
		 *
30
		 * @var Freemius
31
		 */
32
		private $_fs;
33
34
		function __construct( Freemius $fs ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
35
			$this->_fs = $fs;
36
37
			$this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
38
39
			// Remove default plugin information action.
40
			remove_all_actions( 'install_plugins_pre_plugin-information' );
41
42
			// Override action with custom plugins function for add-ons.
43
			add_action( 'install_plugins_pre_plugin-information', array( &$this, 'install_plugin_information' ) );
44
45
			// Override request for plugin information for Add-ons.
46
			add_filter(
47
				'fs_plugins_api',
48
				array( &$this, '_get_addon_info_filter' ),
49
				WP_FS__DEFAULT_PRIORITY, 3 );
50
		}
51
52
		/**
53
		 * Generate add-on plugin information.
54
		 *
55
		 * @author Vova Feldman (@svovaf)
56
		 * @since  1.0.6
57
		 *
58
		 * @param array       $data
59
		 * @param string      $action
60
		 * @param object|null $args
61
		 *
62
		 * @return array|null
63
		 */
64
		function _get_addon_info_filter( $data, $action = '', $args = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
65
			$this->_logger->entrance();
66
67
			$parent_plugin_id = fs_request_get( 'parent_plugin_id', false );
68
69
			if ( $this->_fs->get_id() != $parent_plugin_id ||
70
			     ( 'plugin_information' !== $action ) ||
71
			     ! isset( $args->slug )
72
			) {
73
				return $data;
74
			}
75
76
			// Find add-on by slug.
77
			$selected_addon = $this->_fs->get_addon_by_slug($args->slug, WP_FS__DEV_MODE);
78
79
			if ( false === $selected_addon ) {
80
				return $data;
81
			}
82
83
			if ( ! isset( $selected_addon->info ) ) {
84
				// Setup some default info.
85
				$selected_addon->info                  = new stdClass();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \stdClass() of type object<stdClass> is incompatible with the declared type object<FS_Plugin_Info> of property $info.

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...
86
				$selected_addon->info->selling_point_0 = 'Selling Point 1';
87
				$selected_addon->info->selling_point_1 = 'Selling Point 2';
88
				$selected_addon->info->selling_point_2 = 'Selling Point 3';
89
				$selected_addon->info->description     = '<p>Tell your users all about your add-on</p>';
90
			}
91
92
			fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
93
94
			$data = $args;
95
96
			$is_free = true;
97
98
			// Load add-on pricing.
99
			$has_pricing  = false;
100
			$has_features = false;
101
			$plans        = false;
102
			$plans_result = $this->_fs->get_api_site_or_plugin_scope()->get( "/addons/{$selected_addon->id}/plans.json?type=visible" );
103
			if ( ! isset( $plans_result->error ) ) {
104
				$plans = $plans_result->plans;
105
				if ( is_array( $plans ) ) {
106
					for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) {
107
						$plans[ $i ] = new FS_Plugin_Plan( $plans[ $i ] );
108
						$plan        = $plans[ $i ];
109
110
						$pricing_result = $this->_fs->get_api_site_or_plugin_scope()->get( "/addons/{$selected_addon->id}/plans/{$plan->id}/pricing.json" );
111
						if ( ! isset( $pricing_result->error ) ) {
112
							// Update plan's pricing.
113
							$plan->pricing = $pricing_result->pricing;
114
115
							if ( is_array( $plan->pricing ) && ! empty( $plan->pricing ) ) {
116
								$is_free = false;
117
118
								foreach ( $plan->pricing as &$pricing ) {
119
									$pricing = new FS_Pricing( $pricing );
120
								}
121
							}
122
123
							$has_pricing = true;
124
						}
125
126
						$features_result = $this->_fs->get_api_site_or_plugin_scope()->get( "/addons/{$selected_addon->id}/plans/{$plan->id}/features.json" );
127
						if ( ! isset( $features_result->error ) &&
128
						     is_array( $features_result->features ) &&
129
						     0 < count( $features_result->features )
130
						) {
131
							// Update plan's pricing.
132
							$plan->features = $features_result->features;
0 ignored issues
show
Bug introduced by
The property features does not seem to exist. Did you mean is_block_features?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
133
134
							$has_features = true;
135
						}
136
					}
137
				}
138
			}
139
140
			// Fetch latest version from Freemius.
141
			$latest = $this->_fs->_fetch_latest_version( $selected_addon->id );
142
143
			if ( ! $is_free ) {
144
				// If paid add-on, then it's not on wordpress.org
145
				$is_wordpress_org = false;
146
			} else {
147
				// If no versions found, then assume it's a .org plugin.
148
				$is_wordpress_org = ( false === $latest );
149
			}
150
151
			if ( $is_wordpress_org ) {
152
				$repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository(
153
					'plugin_information', (object) array(
154
					'slug'   => $selected_addon->slug,
155
					'is_ssl' => is_ssl(),
156
					'fields' => array(
157
						'banners'         => true,
158
						'reviews'         => true,
159
						'downloaded'      => false,
160
						'active_installs' => true
161
					)
162
				) );
163
164
				if ( ! empty( $repo_data ) ) {
165
					$data                 = $repo_data;
166
					$data->wp_org_missing = false;
167
				} else {
168
					// Couldn't find plugin on .org.
169
					$is_wordpress_org = false;
170
171
					// Plugin is missing, not on Freemius nor WP.org.
172
					$data->wp_org_missing = true;
173
				}
174
			}
175
176
			if ( ! $is_wordpress_org ) {
177
				$data->checkout_link = $this->_fs->checkout_url();
178
				$data->fs_missing    = ( false === $latest );
179
180
				if ( $is_free ) {
181
					$data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id );
182
				}
183
			}
184
185
			if ( ! $is_wordpress_org ) {
186
// Fetch as much as possible info from local files.
187
				$plugin_local_data = $this->_fs->get_plugin_data();
188
				$data->name        = $selected_addon->title;
189
				$data->author      = $plugin_local_data['Author'];
190
				$view_vars         = array( 'plugin' => $selected_addon );
191
				$data->sections    = array(
192
					'description' => fs_get_template( '/plugin-info/description.php', $view_vars ),
193
				);
194
195
				if ( ! empty( $selected_addon->info->banner_url ) ) {
196
					$data->banners = array(
197
						'low' => $selected_addon->info->banner_url,
198
					);
199
				}
200
201
				if ( ! empty( $selected_addon->info->screenshots ) ) {
202
					$view_vars                     = array(
203
						'screenshots' => $selected_addon->info->screenshots,
204
						'plugin'      => $selected_addon,
205
					);
206
					$data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars );
207
				}
208
209
				if ( is_object( $latest ) ) {
210
					$data->version      = $latest->version;
211
					$data->last_updated = ! is_null( $latest->updated ) ? $latest->updated : $latest->created;
212
					$data->requires     = $latest->requires_platform_version;
213
					$data->tested       = $latest->tested_up_to_version;
214
				} else {
215
					// Add dummy version.
216
					$data->version = '1.0.0';
217
218
					// Add message to developer to deploy the plugin through Freemius.
219
				}
220
			}
221
222
			if ( $has_pricing ) {
223
				// Add plans to data.
224
				$data->plans = $plans;
225
226
				if ( $has_features ) {
227
					$view_vars                  = array(
228
						'plans'  => $plans,
229
						'plugin' => $selected_addon,
230
					);
231
					$data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars );
232
				}
233
			}
234
235
			$data->is_paid  = ! $is_free;
236
			$data->external = ! $is_wordpress_org;
237
238
			return $data;
239
		}
240
241
		/**
242
		 * @author Vova Feldman (@svovaf)
243
		 * @since  1.1.7
244
		 *
245
		 * @param FS_Plugin_Plan $plan
246
		 *
247
		 * @return string
248
		 */
249
		private function get_billing_cycle( FS_Plugin_Plan $plan ) {
250
			$billing_cycle = null;
251
252
			if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) {
253
				$pricing = $plan->pricing[0];
254
				if ( isset( $pricing->annual_price ) ) {
255
					$billing_cycle = 'annual';
256
				} else if ( isset( $pricing->monthly_price ) ) {
257
					$billing_cycle = 'monthly';
258
				} else if ( isset( $pricing->lifetime_price ) ) {
259
					$billing_cycle = 'lifetime';
260
				}
261
			} else {
262
				foreach ( $plan->pricing as $pricing ) {
263
					if ( isset( $pricing->annual_price ) ) {
264
						$billing_cycle = 'annual';
265
					} else if ( isset( $pricing->monthly_price ) ) {
266
						$billing_cycle = 'monthly';
267
					} else if ( isset( $pricing->lifetime_price ) ) {
268
						$billing_cycle = 'lifetime';
269
					}
270
271
					if ( ! is_null( $billing_cycle ) ) {
272
						break;
273
					}
274
				}
275
			}
276
277
			return $billing_cycle;
278
		}
279
280
		/**
281
		 * @author Vova Feldman (@svovaf)
282
		 * @since  1.1.7
283
		 *
284
		 * @param FS_Plugin_Plan $plan
285
		 * @param FS_Pricing     $pricing
286
		 *
287
		 * @return float|null|string
288
		 */
289
		private function get_price_tag( FS_Plugin_Plan $plan, FS_Pricing $pricing ) {
290
			$price_tag = '';
291
			if ( isset( $pricing->annual_price ) ) {
292
				$price_tag = $pricing->annual_price . ( $plan->is_block_features ? ' / year' : '' );
293
			} else if ( isset( $pricing->monthly_price ) ) {
294
				$price_tag = $pricing->monthly_price . ' / mo';
295
			} else if ( isset( $pricing->lifetime_price ) ) {
296
				$price_tag = $pricing->lifetime_price;
297
			}
298
299
			return '$' . $price_tag;
300
		}
301
302
		/**
303
		 * @author Vova Feldman (@svovaf)
304
		 * @since  1.1.7
305
		 *
306
		 * @param object              $api
307
		 * @param FS_Plugin_Plan|null $plan
308
		 *
309
		 * @return string
310
		 */
311
		private function get_plugin_cta( $api, $plan = null ) {
312
			if ( ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) ) {
313
314
				if ( ! empty( $api->checkout_link ) && isset( $api->plans ) && 0 < is_array( $api->plans ) ) {
315
					if ( is_null( $plan ) ) {
316
						$plan = $api->plans[0];
317
					}
318
319
					return ' <a class="button button-primary right" href="' . $this->_fs->addon_checkout_url(
320
						$plan->plugin_id,
321
						$plan->pricing[0]->id,
322
						$this->get_billing_cycle( $plan ),
323
						$plan->has_trial()
324
					) . '" target="_parent">' .
325
					       ( ! $plan->has_trial() ?
326
						       fs_text( 'purchase', $api->slug ) :
327
						       sprintf( fs_text( 'start-free-x', $api->slug ), $this->get_trial_period( $plan ) )
328
					       ) .
329
					       '</a>';
330
331
					// @todo Add Cart concept.
332
//			echo ' <a class="button right" href="' . $status['url'] . '" target="_parent">' . __( 'Add to Cart' ) . '</a>';
333
334
				} else if ( ! empty( $api->download_link ) ) {
335
					$status = install_plugin_install_status( $api );
336
337
					// Hosted on WordPress.org.
338
					switch ( $status['status'] ) {
339
						case 'install':
340
							if ( $api->external &&
341
							     $this->_fs->is_org_repo_compliant() ||
342
							     ! $this->_fs->is_premium()
343
							) {
344
								/**
345
								 * Add-on hosted on Freemius, not yet installed, and core
346
								 * plugin is wordpress.org compliant. Therefore, require a download
347
								 * since installing external plugins is not allowed by the wp.org guidelines.
348
								 */
349
								return ' <a class="button button-primary right" href="' . esc_url( $api->download_link ) . '" target="_blank">' . fs_text( 'download-latest', $api->slug ) . '</a>';
350
							} else {
351
								if ( $status['url'] ) {
352
									return '<a class="button button-primary right" href="' . $status['url'] . '" target="_parent">' . fs_text( 'install-now', $api->slug ) . '</a>';
353
								}
354
							}
355
							break;
356
						case 'update_available':
357
							if ( $status['url'] ) {
358
								return '<a class="button button-primary right" href="' . $status['url'] . '" target="_parent">' . fs_text( 'install-update-now', $api->slug ) . '</a>';
359
							}
360
							break;
361
						case 'newer_installed':
362
							return '<a class="button button-primary right disabled">' . sprintf( fs_text( 'newer-installed', $api->slug ), $status['version'] ) . '</a>';
363
							break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
364
						case 'latest_installed':
365
							return '<a class="button button-primary right disabled">' . fs_text( 'latest-installed', $api->slug ) . '</a>';
366
							break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
367
					}
368
369
				}
370
			}
371
372
			return '';
373
		}
374
375
		/**
376
		 * @author Vova Feldman (@svovaf)
377
		 * @since  1.1.7
378
		 *
379
		 * @param FS_Plugin_Plan $plan
380
		 *
381
		 * @return string
382
		 */
383
		private function get_trial_period( $plan ) {
384
			$trial_period = (int) $plan->trial_period;
385
386
			switch ( $trial_period ) {
387
				case 30:
388
					return 'month';
389
				case 60:
390
					return '2 months';
391
				default:
392
					return "{$plan->trial_period} days";
393
			}
394
		}
395
396
		/**
397
		 * Display plugin information in dialog box form.
398
		 *
399
		 * Based on core install_plugin_information() function.
400
		 *
401
		 * @author Vova Feldman (@svovaf)
402
		 * @since  1.0.6
403
		 */
404
		function install_plugin_information() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Coding Style introduced by
install_plugin_information 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...
Coding Style introduced by
install_plugin_information uses the super-global variable $GLOBALS 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...
405
			global $tab;
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...
406
407
			if ( empty( $_REQUEST['plugin'] ) ) {
408
				return;
409
			}
410
411
			$args = array(
412
				'slug'   => wp_unslash( $_REQUEST['plugin'] ),
413
				'is_ssl' => is_ssl(),
414
				'fields' => array(
415
					'banners'         => true,
416
					'reviews'         => true,
417
					'downloaded'      => false,
418
					'active_installs' => true
419
				)
420
			);
421
422
			if ( is_array( $args ) ) {
423
				$args = (object) $args;
424
			}
425
426
			if ( ! isset( $args->per_page ) ) {
427
				$args->per_page = 24;
428
			}
429
430
			if ( ! isset( $args->locale ) ) {
431
				$args->locale = get_locale();
432
			}
433
434
			$api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args );
435
436
			if ( is_wp_error( $api ) ) {
437
				wp_die( $api );
438
			}
439
440
			$plugins_allowedtags = array(
441
				'a'       => array(
442
					'href'   => array(),
443
					'title'  => array(),
444
					'target' => array(),
445
					// Add image style for screenshots.
446
					'class'  => array()
447
				),
448
				'style'   => array(),
449
				'abbr'    => array( 'title' => array() ),
450
				'acronym' => array( 'title' => array() ),
451
				'code'    => array(),
452
				'pre'     => array(),
453
				'em'      => array(),
454
				'strong'  => array(),
455
				'div'     => array( 'class' => array() ),
456
				'span'    => array( 'class' => array() ),
457
				'p'       => array(),
458
				'ul'      => array(),
459
				'ol'      => array(),
460
				'li'      => array( 'class' => array() ),
461
				'i'       => array( 'class' => array() ),
462
				'h1'      => array(),
463
				'h2'      => array(),
464
				'h3'      => array(),
465
				'h4'      => array(),
466
				'h5'      => array(),
467
				'h6'      => array(),
468
				'img'     => array( 'src' => array(), 'class' => array(), 'alt' => array() ),
469
//			'table' => array(),
0 ignored issues
show
Unused Code Comprehensibility introduced by
51% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
470
//			'td' => array(),
471
//			'tr' => array(),
472
//			'th' => array(),
473
//			'thead' => array(),
474
//			'tbody' => array(),
475
			);
476
477
			$plugins_section_titles = array(
478
				'description'  => fs_text( 'description', $api->slug ),
479
				'installation' => fs_text( 'installation', $api->slug ),
480
				'faq'          => fs_text( 'faq', $api->slug ),
481
				'screenshots'  => fs_text( 'screenshots', $api->slug ),
482
				'changelog'    => fs_text( 'changelog', $api->slug ),
483
				'reviews'      => fs_text( 'reviews', $api->slug ),
484
				'other_notes'  => fs_text( 'other_notes', $api->slug ),
485
			);
486
487
			// Sanitize HTML
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
488
//		foreach ( (array) $api->sections as $section_name => $content ) {
489
//			$api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags );
490
//		}
491
492
			foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) {
493
				if ( isset( $api->$key ) ) {
494
					$api->$key = wp_kses( $api->$key, $plugins_allowedtags );
495
				}
496
			}
497
498
			// Add after $api->slug is ready.
499
			$plugins_section_titles['features'] = fs_text( 'features-and-pricing', $api->slug );
500
501
			$_tab = esc_attr( $tab );
502
503
			$section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English.
504
			if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) {
505
				$section_titles = array_keys( (array) $api->sections );
506
				$section        = array_shift( $section_titles );
507
			}
508
509
			iframe_header( fs_text( 'plugin-install', $api->slug ) );
510
511
			$_with_banner = '';
512
513
//	var_dump($api->banners);
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
514
			if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) {
515
				$_with_banner = 'with-banner';
516
				$low          = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low'];
517
				$high         = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high'];
518
				?>
519
				<style type="text/css">
520
					#plugin-information-title.with-banner
521
					{
522
						background-image: url( <?php echo esc_url( $low ); ?> );
523
					}
524
525
					@media only screen and ( -webkit-min-device-pixel-ratio: 1.5 )
526
					{
527
						#plugin-information-title.with-banner
528
						{
529
							background-image: url( <?php echo esc_url( $high ); ?> );
530
						}
531
					}
532
				</style>
533
				<?php
534
			}
535
536
			echo '<div id="plugin-information-scrollable">';
537
			echo "<div id='{$_tab}-title' class='{$_with_banner}'><div class='vignette'></div><h2>{$api->name}</h2></div>";
538
			echo "<div id='{$_tab}-tabs' class='{$_with_banner}'>\n";
539
540
			foreach ( (array) $api->sections as $section_name => $content ) {
541
				if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) {
542
					continue;
543
				}
544
545
				if ( isset( $plugins_section_titles[ $section_name ] ) ) {
546
					$title = $plugins_section_titles[ $section_name ];
547
				} else {
548
					$title = ucwords( str_replace( '_', ' ', $section_name ) );
549
				}
550
551
				$class       = ( $section_name === $section ) ? ' class="current"' : '';
552
				$href        = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) );
553
				$href        = esc_url( $href );
554
				$san_section = esc_attr( $section_name );
555
				echo "\t<a name='$san_section' href='$href' $class>$title</a>\n";
556
			}
557
558
			echo "</div>\n";
559
560
			?>
561
		<div id="<?php echo $_tab; ?>-content" class='<?php echo $_with_banner; ?>'>
562
			<div class="fyi">
563
				<?php if ( $api->is_paid ) : ?>
564
					<?php if ( isset( $api->plans ) ) : ?>
565
						<div class="plugin-information-pricing">
566
						<?php foreach ( $api->plans as $plan ) : ?>
567
							<?php
568
							/**
569
							 * @var FS_Plugin_Plan $plan
570
							 */
571
							?>
572
							<?php $first_pricing = $plan->pricing[0] ?>
573
							<?php $is_multi_cycle = $first_pricing->is_multi_cycle() ?>
574
							<div class="fs-plan<?php if ( ! $is_multi_cycle ) {
575
								echo ' fs-single-cycle';
576
							} ?>" data-plan-id="<?php echo $plan->id ?>">
577
								<h3 data-plan="<?php echo $plan->id ?>"><?php printf( fs_text( 'x-plan', $api->slug ), $plan->title ) ?></h3>
578
								<?php $has_annual = $first_pricing->has_annual() ?>
579
								<?php $has_monthly = $first_pricing->has_monthly() ?>
580
								<div class="nav-tab-wrapper">
581
									<?php $billing_cycles = array( 'monthly', 'annual', 'lifetime' ) ?>
582
									<?php $i = 0;
583
										foreach ( $billing_cycles as $cycle ) : ?>
584
											<?php $prop = "{$cycle}_price";
585
											if ( isset( $first_pricing->{$prop} ) ) : ?>
586
												<?php $is_featured = ( 'annual' === $cycle && $is_multi_cycle ) ?>
587
												<?php
588
												$prices = array();
589
												foreach ( $plan->pricing as $pricing ) {
590
													if ( isset( $pricing->{$prop} ) ) {
591
														$prices[] = array(
592
															'id'       => $pricing->id,
593
															'licenses' => $pricing->licenses,
594
															'price'    => $pricing->{$prop}
595
														);
596
													}
597
												}
598
												?>
599
												<a class="nav-tab" data-billing-cycle="<?php echo $cycle ?>"
600
												   data-pricing="<?php echo esc_attr( json_encode( $prices ) ) ?>">
601
													<?php if ( $is_featured ) : ?>
602
														<label>&#9733; <?php fs_echo( 'best', $api->slug ) ?> &#9733;</label>
603
													<?php endif ?>
604
													<?php fs_echo( $cycle, $api->slug ) ?>
605
												</a>
606
											<?php endif ?>
607
											<?php $i ++; endforeach ?>
608
									<?php wp_enqueue_script( 'jquery' ) ?>
609
									<script type="text/javascript">
610
										(function ($, undef) {
611
											var
612
												_formatBillingFrequency = function (cycle) {
613
													switch (cycle) {
614
														case 'monthly':
615
															return '<?php printf(fs_text('billed-x', $api->slug), fs_text('monthly', $api->slug)) ?>';
616
														case 'annual':
617
															return '<?php printf(fs_text('billed-x', $api->slug), fs_text('annually', $api->slug)) ?>';
618
														case 'lifetime':
619
															return '<?php printf(fs_text('billed-x', $api->slug), fs_text('once', $api->slug)) ?>';
620
													}
621
												},
622
												_formatLicensesTitle = function (pricing) {
623
													switch (pricing.licenses) {
624
														case 1:
625
															return '<?php fs_echo( 'license-single-site', $api->slug ) ?>';
626
														case null:
627
															return '<?php fs_echo( 'license-unlimited', $api->slug ) ?>';
628
														default:
629
															return '<?php fs_echo( 'license-x-sites', $api->slug ) ?>'.replace('%s', pricing.licenses);
630
													}
631
												},
632
												_formatPrice = function (pricing, cycle, multipleLicenses) {
633
													if (undef === multipleLicenses)
634
														multipleLicenses = true;
635
636
													var priceCycle;
637
													switch (cycle) {
638
														case 'monthly':
639
															priceCycle = ' / <?php fs_echo('mo', $api->slug) ?>';
640
															break;
641
														case 'lifetime':
642
															priceCycle = '';
643
															break;
644
														case 'annual':
645
														default:
646
															priceCycle = ' / <?php fs_echo('year', $api->slug) ?>';
647
															break;
648
													}
649
650
													if (!multipleLicenses && 1 == pricing.licenses) {
651
														return '$' + pricing.price + priceCycle;
652
													}
653
654
													return _formatLicensesTitle(pricing) + ' - <var class="fs-price">$' + pricing.price + priceCycle + '</var>';
655
												},
656
												_checkoutUrl = function (plan, pricing, cycle) {
657
													return '<?php echo esc_url_raw(remove_query_arg('billing_cycle', add_query_arg(array('plugin_id' => $plan->plugin_id), $api->checkout_link))) ?>' +
658
														'&plan_id=' + plan +
659
														'&pricing_id=' + pricing +
660
														'&billing_cycle=' + cycle<?php if ($plan->has_trial()) { echo " + '&trial=true'"; }?>;
661
												},
662
												_updateCtaUrl = function (plan, pricing, cycle) {
663
													$('.plugin-information-pricing .button, #plugin-information-footer .button').attr('href', _checkoutUrl(plan, pricing, cycle));
664
												};
665
666
											$(document).ready(function () {
667
												var $plan = $('.plugin-information-pricing .fs-plan[data-plan-id=<?php echo $plan->id ?>]');
668
												$plan.find('input[type=radio]').live('click', function () {
669
													_updateCtaUrl(
670
														$plan.attr('data-plan-id'),
671
														$(this).val(),
672
														$plan.find('.nav-tab-active').attr('data-billing-cycle')
673
													);
674
675
													$plan.find('.fs-trial-terms .fs-price').html(
676
														$(this).parents('label').find('.fs-price').html()
677
													);
678
												});
679
680
												$plan.find('.nav-tab').click(function () {
681
													if ($(this).hasClass('nav-tab-active'))
682
														return;
683
684
													var $this = $(this),
685
													    billingCycle = $this.attr('data-billing-cycle'),
686
													    pricing = JSON.parse($this.attr('data-pricing')),
687
													    $pricesList = $this.parents('.fs-plan').find('.fs-pricing-body .fs-licenses'),
688
													    html = '';
689
690
													// Un-select previously selected tab.
691
													$plan.find('.nav-tab').removeClass('nav-tab-active');
692
693
													// Select current tab.
694
													$this.addClass('nav-tab-active');
695
696
													// Render licenses prices.
697
													if (1 == pricing.length) {
698
														html = '<li><label><?php fs_echo( 'price', $api->slug ) ?>: ' + _formatPrice(pricing[0], billingCycle, false) + '</label></li>';
699
													} else {
700
														for (var i = 0; i < pricing.length; i++) {
701
															html += '<li><label><input name="pricing-<?php echo $plan->id ?>" type="radio" value="' + pricing[i].id + '">' + _formatPrice(pricing[i], billingCycle) + '</label></li>';
702
														}
703
													}
704
													$pricesList.html(html);
705
706
													if (1 < pricing.length) {
707
														// Select first license option.
708
														$pricesList.find('li:first input').click();
709
													}
710
													else {
711
														_updateCtaUrl(
712
															$plan.attr('data-plan-id'),
713
															pricing[0].id,
714
															billingCycle
715
														);
716
													}
717
718
													// Update billing frequency.
719
													$plan.find('.fs-billing-frequency').html(_formatBillingFrequency(billingCycle));
720
721
													if ('annual' === billingCycle) {
722
														$plan.find('.fs-annual-discount').show();
723
													} else {
724
														$plan.find('.fs-annual-discount').hide();
725
													}
726
												});
727
728
												<?php if ( $has_annual ) : ?>
729
												// Select annual by default.
730
												$plan.find('.nav-tab[data-billing-cycle=annual]').click();
731
												<?php else : ?>
732
												// Select first tab.
733
												$plan.find('.nav-tab:first').click();
734
												<?php endif ?>
735
											});
736
										}(jQuery));
737
									</script>
738
								</div>
739
								<div class="fs-pricing-body">
740
									<span class="fs-billing-frequency"></span>
741
									<?php $annual_discount = ( $has_annual && $has_monthly ) ? $plan->pricing[0]->annual_discount_percentage() : 0 ?>
742
									<?php if ( $annual_discount > 0 ) : ?>
743
										<span
744
											class="fs-annual-discount"><?php printf( fs_text( 'save-x', $api->slug ), $annual_discount . '%' ) ?></span>
745
									<?php endif ?>
746
									<ul class="fs-licenses">
747
									</ul>
748
									<?php echo $this->get_plugin_cta( $api, $plan ) ?>
749
									<div style="clear:both"></div>
750
									<?php if ( $plan->has_trial() ) : ?>
751
										<?php $trial_period = $this->get_trial_period( $plan ) ?>
752
										<ul class="fs-trial-terms">
753
											<li>
754
												<i class="dashicons dashicons-yes"></i><?php printf( fs_text( 'no-commitment-x', $api->slug ), $trial_period ) ?>
755
											</li>
756
											<li>
757
												<i class="dashicons dashicons-yes"></i><?php printf( fs_text( 'after-x-pay-as-little-y', $api->slug ), $trial_period, '<var class="fs-price">' . $this->get_price_tag( $plan, $plan->pricing[0] ) . '</var>' ) ?>
758
											</li>
759
										</ul>
760
									<?php endif ?>
761
								</div>
762
							</div>
763
							</div>
764
						<?php endforeach ?>
765
					<?php endif ?>
766
				<?php endif ?>
767
				<div>
768
					<h3><?php fs_echo( 'details', $api->slug ) ?></h3>
769
					<ul>
770
						<?php if ( ! empty( $api->version ) ) { ?>
771
							<li><strong><?php fs_echo( 'version', $api->slug ); ?>:</strong> <?php echo $api->version; ?></li>
772
							<?php
773
						}
774
							if ( ! empty( $api->author ) ) {
775
								?>
776
								<li>
777
									<strong><?php fs_echo( 'author:', $api->slug ); ?></strong> <?php echo links_add_target( $api->author, '_blank' ); ?>
778
								</li>
779
								<?php
780
							}
781
							if ( ! empty( $api->last_updated ) ) {
782
								?>
783
								<li><strong><?php fs_echo( 'last-updated:', $api->slug ); ?></strong> <span
784
										title="<?php echo $api->last_updated; ?>">
785
				<?php printf( fs_text( 'x-ago', $api->slug ), human_time_diff( strtotime( $api->last_updated ) ) ); ?>
786
			</span></li>
787
								<?php
788
							}
789
							if ( ! empty( $api->requires ) ) {
790
								?>
791
								<li>
792
									<strong><?php fs_echo( 'requires-wordpress-version:', $api->slug ); ?></strong> <?php printf( fs_text( 'x-or-higher', $api->slug ), $api->requires ); ?>
793
								</li>
794
								<?php
795
							}
796
							if ( ! empty( $api->tested ) ) {
797
								?>
798
								<li><strong><?php fs_echo( 'compatible-up-to:', $api->slug ); ?></strong> <?php echo $api->tested; ?>
799
								</li>
800
								<?php
801
							}
802
							if ( ! empty( $api->downloaded ) ) {
803
								?>
804
								<li>
805
									<strong><?php fs_echo( 'downloaded:', $api->slug ); ?></strong> <?php printf(
806
										fs_text( ( 1 == $api->downloaded ) ? 'x-time' : 'x-times', $api->slug ),
807
										number_format_i18n( $api->downloaded )
808
									); ?>
809
								</li>
810
								<?php
811
							}
812
							if ( ! empty( $api->slug ) && empty( $api->external ) ) {
813
								?>
814
								<li><a target="_blank"
815
								       href="https://wordpress.org/plugins/<?php echo $api->slug; ?>/"><?php fs_echo( 'wp-org-plugin-page', $api->slug ); ?> &#187;</a>
816
								</li>
817
								<?php
818
							}
819
							if ( ! empty( $api->homepage ) ) {
820
								?>
821
								<li><a target="_blank"
822
								       href="<?php echo esc_url( $api->homepage ); ?>"><?php fs_echo( 'plugin-homepage', $api->slug ); ?> &#187;</a>
823
								</li>
824
								<?php
825
							}
826
							if ( ! empty( $api->donate_link ) && empty( $api->contributors ) ) {
827
								?>
828
								<li><a target="_blank"
829
								       href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_echo( 'donate-to-plugin', $api->slug ); ?> &#187;</a>
830
								</li>
831
							<?php } ?>
832
					</ul>
833
				</div>
834
				<?php if ( ! empty( $api->rating ) ) { ?>
835
					<h3><?php fs_echo( 'average-rating', $api->slug ); ?></h3>
836
					<?php wp_star_rating( array(
837
						'rating' => $api->rating,
838
						'type'   => 'percent',
839
						'number' => $api->num_ratings
840
					) ); ?>
841
					<small>(<?php printf(
842
							fs_text( 'based-on-x', $api->slug ),
843
							sprintf(
844
								fs_text( ( 1 == $api->num_ratings ) ? 'x-rating' : 'x-ratings', $api->slug ),
845
								number_format_i18n( $api->num_ratings )
846
							) ); ?>)</small>
847
					<?php
848
				}
849
850
					if ( ! empty( $api->ratings ) && array_sum( (array) $api->ratings ) > 0 ) {
851
						foreach ( $api->ratings as $key => $ratecount ) {
852
							// Avoid div-by-zero.
853
							$_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0;
854
							$stars_label = sprintf(
855
								fs_text( ( 1 == $key ) ? 'x-star' : 'x-stars', $api->slug ),
856
								number_format_i18n( $key )
857
							);
858
							?>
859
							<div class="counter-container">
860
					<span class="counter-label"><a
861
							href="https://wordpress.org/support/view/plugin-reviews/<?php echo $api->slug; ?>?filter=<?php echo $key; ?>"
862
							target="_blank"
863
							title="<?php echo esc_attr( sprintf( fs_text('click-to-reviews', $api->slug), $stars_label) ) ?>"><?php echo $stars_label ?></a></span>
864
								<span class="counter-back">
865
						<span class="counter-bar" style="width: <?php echo 92 * $_rating; ?>px;"></span>
866
					</span>
867
								<span class="counter-count"><?php echo number_format_i18n( $ratecount ); ?></span>
868
							</div>
869
							<?php
870
						}
871
					}
872
					if ( ! empty( $api->contributors ) ) {
873
						?>
874
						<h3><?php fs_echo( 'contributors', $api->slug ); ?></h3>
875
						<ul class="contributors">
876
							<?php
877
								foreach ( (array) $api->contributors as $contrib_username => $contrib_profile ) {
878
									if ( empty( $contrib_username ) && empty( $contrib_profile ) ) {
879
										continue;
880
									}
881
									if ( empty( $contrib_username ) ) {
882
										$contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile );
883
									}
884
									$contrib_username = sanitize_user( $contrib_username );
885
									if ( empty( $contrib_profile ) ) {
886
										echo "<li><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</li>";
887
									} else {
888
										echo "<li><a href='{$contrib_profile}' target='_blank'><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</a></li>";
889
									}
890
								}
891
							?>
892
						</ul>
893
						<?php if ( ! empty( $api->donate_link ) ) { ?>
894
							<a target="_blank"
895
							   href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_echo( 'donate-to-plugin', $api->slug ) ?> &#187;</a>
896
						<?php } ?>
897
					<?php } ?>
898
			</div>
899
			<div id="section-holder" class="wrap">
900
			<?php
901
			if ( ! empty( $api->tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) {
902
				echo '<div class="notice notice-warning"><p>' . '<strong>' . fs_text( 'warning:', $api->slug ) . '</strong> ' . fs_text( 'not-tested-warning', $api->slug ) . '</p></div>';
903
			} else if ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) {
904
				echo '<div class="notice notice-warning"><p>' . '<strong>' . fs_text( 'warning:', $api->slug ) . '</strong> ' . fs_text( 'not-compatible-warning', $api->slug ) . '</p></div>';
905
			}
906
907
			foreach ( (array) $api->sections as $section_name => $content ) {
908
				$content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' );
909
				$content = links_add_target( $content, '_blank' );
910
911
				$san_section = esc_attr( $section_name );
912
913
				$display = ( $section_name === $section ) ? 'block' : 'none';
914
915
				if ( 'description' === $section_name &&
916
				     ( ( ! $api->external && $api->wp_org_missing ) ||
917
				       ( $api->external && $api->fs_missing ) )
918
				) {
919
					$missing_notice = array(
920
						'type'    => 'error',
921
						'id'      => md5( microtime() ),
922
						'message' => fs_text( ( $api->is_paid ? 'paid-addon-not-deployed' : 'free-addon-not-deployed' ), $api->slug ),
923
					);
924
					fs_require_template( 'admin-notice.php', $missing_notice );
925
				}
926
				echo "\t<div id='section-{$san_section}' class='section' style='display: {$display};'>\n";
927
				echo $content;
928
				echo "\t</div>\n";
929
			}
930
			echo "</div>\n";
931
			echo "</div>\n";
932
			echo "</div>\n"; // #plugin-information-scrollable
933
			echo "<div id='$tab-footer'>\n";
934
935
			echo $this->get_plugin_cta( $api );
936
937
			echo "</div>\n";
938
939
			iframe_footer();
940
			exit;
941
		}
942
	}