| 1 |
<?php |
| 2 |
namespace EverCompare\Admin; |
| 3 |
|
| 4 |
defined( 'ABSPATH' ) || exit; |
| 5 |
/** |
| 6 |
* Recommended Plugins handlers class |
| 7 |
*/ |
| 8 |
class Recommended_Plugins { |
| 9 |
|
| 10 |
/** |
| 11 |
* [$_instance] |
| 12 |
* @var null |
| 13 |
*/ |
| 14 |
private static $_instance = null; |
| 15 |
|
| 16 |
/** |
| 17 |
* [$plugins_allowedtags] allow tag |
| 18 |
* @var array |
| 19 |
*/ |
| 20 |
public $plugins_allowedtags = array( |
| 21 |
'a' => array( |
| 22 |
'href' => array(), |
| 23 |
'title' => array(), |
| 24 |
'target' => array(), |
| 25 |
), |
| 26 |
'abbr' => array( 'title' => array() ), |
| 27 |
'acronym' => array( 'title' => array() ), |
| 28 |
'code' => array(), |
| 29 |
'pre' => array(), |
| 30 |
'em' => array(), |
| 31 |
'strong' => array(), |
| 32 |
'ul' => array(), |
| 33 |
'ol' => array(), |
| 34 |
'li' => array(), |
| 35 |
'p' => array(), |
| 36 |
'br' => array(), |
| 37 |
); |
| 38 |
|
| 39 |
/** |
| 40 |
* Veriable Initialize |
| 41 |
*/ |
| 42 |
public $text_domain = ''; |
| 43 |
public $parent_menu_slug = ''; |
| 44 |
public $menu_label = ''; |
| 45 |
public $menu_page_slug = ''; |
| 46 |
public $menu_capability = ''; |
| 47 |
public $priority = ''; |
| 48 |
public $hook_suffix = ''; |
| 49 |
public $assets_url = ''; |
| 50 |
public $tab_list = []; |
| 51 |
|
| 52 |
/** |
| 53 |
* [instance] Initializes a singleton instance |
| 54 |
* @return [Recommended_Plugins] |
| 55 |
*/ |
| 56 |
public static function instance( $args = [] ) { |
| 57 |
if ( is_null( self::$_instance ) ) { |
| 58 |
self::$_instance = new self( $args ); |
| 59 |
} |
| 60 |
return self::$_instance; |
| 61 |
} |
| 62 |
|
| 63 |
/** |
| 64 |
* [__construct] Class construct |
| 65 |
*/ |
| 66 |
function __construct( $args ) { |
| 67 |
|
| 68 |
// Initialize properties |
| 69 |
$this->text_domain = !empty( $args['text_domain'] ) ? $args['text_domain'] : 'htrp'; |
| 70 |
$this->parent_menu_slug = !empty( $args['parent_menu_slug'] ) ? $args['parent_menu_slug'] : 'plugins.php'; |
| 71 |
$this->menu_label = !empty( $args['menu_label'] ) ? $args['menu_label'] : esc_html__( 'Recommendations', 'ever-compare' ); |
| 72 |
$this->menu_capability = !empty( $args['menu_capability'] ) ? $args['menu_capability'] : 'manage_options'; |
| 73 |
$this->menu_page_slug = !empty( $args['menu_page_slug'] ) ? $args['menu_page_slug'] : $this->text_domain . '_extensions'; |
| 74 |
$this->priority = !empty( $args['priority'] ) ? $args['priority'] : 100; |
| 75 |
$this->hook_suffix = !empty( $args['hook_suffix'] ) ? $args['hook_suffix'] : ''; |
| 76 |
$this->assets_url = !empty( $args['assets_url'] ) ? $args['assets_url'] : plugins_url( '', __FILE__ ); |
| 77 |
$this->tab_list = !empty( $args['tab_list'] ) ? $args['tab_list'] : []; |
| 78 |
|
| 79 |
|
| 80 |
add_action( 'admin_menu', [ $this, 'admin_menu' ], $this->priority ); |
| 81 |
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); |
| 82 |
|
| 83 |
// Ajax Action |
| 84 |
add_action( 'wp_ajax_'.$this->text_domain.'_ajax_plugin_activation', [ $this, 'plugin_activation' ] ); |
| 85 |
|
| 86 |
} |
| 87 |
|
| 88 |
/** |
| 89 |
* [admin_menu] Add Recommended Menu |
| 90 |
* @return [void] |
| 91 |
*/ |
| 92 |
public function admin_menu(){ |
| 93 |
add_submenu_page( |
| 94 |
$this->parent_menu_slug, |
| 95 |
$this->menu_label, |
| 96 |
$this->menu_label, |
| 97 |
$this->menu_capability, |
| 98 |
$this->menu_page_slug, |
| 99 |
[ $this, 'render_html' ] |
| 100 |
); |
| 101 |
} |
| 102 |
|
| 103 |
/** |
| 104 |
* [enqueue_assets] |
| 105 |
* @param [string] $hook_suffix Current page hook |
| 106 |
* @return [void] |
| 107 |
*/ |
| 108 |
public function enqueue_assets( $hook_suffix ) { |
| 109 |
if( $this->hook_suffix ){ |
| 110 |
if( $this->hook_suffix == $hook_suffix ){ |
| 111 |
wp_enqueue_script( 'htrp-plugin-install-manager', $this->assets_url . '/js/plugins_install_manager.js', array('jquery','wp-util', 'updates'), '1.0.0', true ); |
| 112 |
} |
| 113 |
} else { |
| 114 |
wp_enqueue_script( 'htrp-plugin-install-manager', $this->assets_url . '/js/plugins_install_manager.js', array('jquery','wp-util', 'updates'), '1.0.0', true ); |
| 115 |
} |
| 116 |
|
| 117 |
/** |
| 118 |
* Thickbox assest |
| 119 |
*/ |
| 120 |
add_thickbox(); |
| 121 |
|
| 122 |
/** |
| 123 |
* localize data |
| 124 |
*/ |
| 125 |
$localize_vars['ajaxurl'] = admin_url('admin-ajax.php'); |
| 126 |
$localize_vars['text_domain'] = sanitize_title_with_dashes( $this->text_domain ); |
| 127 |
$localize_vars['nonce'] = wp_create_nonce('htrp_nonce'); |
| 128 |
$localize_vars['buttontxt'] = array( |
| 129 |
'buynow' => esc_html__( 'Buy Now', 'ever-compare' ), |
| 130 |
'preview' => esc_html__( 'Preview', 'ever-compare' ), |
| 131 |
'installing' => esc_html__( 'Installing..', 'ever-compare' ), |
| 132 |
'activating' => esc_html__( 'Activating..', 'ever-compare' ), |
| 133 |
'active' => esc_html__( 'Activated', 'ever-compare' ), |
| 134 |
); |
| 135 |
wp_localize_script( 'htrp-plugin-install-manager', 'htrp_params', $localize_vars ); |
| 136 |
|
| 137 |
} |
| 138 |
|
| 139 |
/** |
| 140 |
* [add_new_tab] |
| 141 |
* @param [void] set tab content |
| 142 |
*/ |
| 143 |
public function add_new_tab( $tab_list ){ |
| 144 |
$this->tab_list[] = $tab_list; |
| 145 |
} |
| 146 |
|
| 147 |
/** |
| 148 |
* [render_html] |
| 149 |
* @return [void] Render HTML |
| 150 |
*/ |
| 151 |
public function render_html(){ |
| 152 |
if ( ! function_exists('plugins_api') ){ include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); } |
| 153 |
|
| 154 |
$requested_slugs = array(); |
| 155 |
foreach ( $this->tab_list as $tab ) { |
| 156 |
if ( empty( $tab['plugins'] ) ) { continue; } |
| 157 |
foreach ( $tab['plugins'] as $plugin ) { |
| 158 |
if ( ! empty( $plugin['slug'] ) ) { $requested_slugs[] = $plugin['slug']; } |
| 159 |
} |
| 160 |
} |
| 161 |
$prepare_plugin = $this->get_plugins_info( $requested_slugs ); |
| 162 |
|
| 163 |
?> |
| 164 |
<div class="wrap"> |
| 165 |
<h2><?php echo esc_html(get_admin_page_title()); ?></h2> |
| 166 |
<style> |
| 167 |
.htrp-admin-tab-pane{ |
| 168 |
display: none; |
| 169 |
} |
| 170 |
/* Grid layout is normally provided by core's .plugin-install-php #the-list rule, |
| 171 |
which is scoped to WP's own Add Plugins screen and never matches this page - |
| 172 |
define it ourselves so the cards don't just stack in a single column. */ |
| 173 |
.htrp-admin-tab-pane.htrp-active{ |
| 174 |
display: flex; |
| 175 |
flex-wrap: wrap; |
| 176 |
gap: 16px; |
| 177 |
} |
| 178 |
.plugin-card{ |
| 179 |
display: flex; |
| 180 |
flex-direction: column; |
| 181 |
justify-content: space-between; |
| 182 |
} |
| 183 |
.plugin-card h3 a{ |
| 184 |
text-decoration: none; |
| 185 |
} |
| 186 |
.htrp-extension-admin-tab-area .filter-links li>a:focus, .htrp-extension-admin-tab-area .filter-links li>a:hover { |
| 187 |
color: inherit; |
| 188 |
box-shadow: none; |
| 189 |
} |
| 190 |
.filter-links .htrp-active{ |
| 191 |
box-shadow: none; |
| 192 |
border-bottom: 4px solid #646970; |
| 193 |
color: #1d2327; |
| 194 |
} |
| 195 |
.downloaded-count{ |
| 196 |
display: block; |
| 197 |
margin-top:5px; |
| 198 |
} |
| 199 |
</style> |
| 200 |
|
| 201 |
<div class="htrp-extension-admin-tab-area wp-filter"> |
| 202 |
<ul class="htrp-admin-tabs filter-links"> |
| 203 |
<?php |
| 204 |
foreach( $this->tab_list as $tab ){ |
| 205 |
$active_class = isset( $tab['active'] ) && $tab['active'] ? 'htrp-active' : ''; |
| 206 |
?> |
| 207 |
<li> |
| 208 |
<a href="#<?php echo esc_attr( sanitize_title_with_dashes( $tab['title'] ) ) ?>" class="<?php echo esc_attr( $active_class ) ?>"><?php echo esc_html( $tab['title'] ) ?></a> |
| 209 |
</li> |
| 210 |
<?php |
| 211 |
} |
| 212 |
?> |
| 213 |
</ul> |
| 214 |
</div> |
| 215 |
|
| 216 |
<?php |
| 217 |
$plugins_type = ''; |
| 218 |
foreach( $this->tab_list as $tab ): |
| 219 |
|
| 220 |
$active_class = isset( $tab['active'] ) && $tab['active'] ? 'htrp-active' : ''; |
| 221 |
$plugins = $tab['plugins']; |
| 222 |
|
| 223 |
echo '<div id="'.esc_attr( sanitize_title_with_dashes( $tab['title'] ) ).'" class="htrp-admin-tab-pane '.esc_attr( $active_class ).'">'; |
| 224 |
foreach( $plugins as $plugin ): |
| 225 |
|
| 226 |
$data = array( |
| 227 |
'slug' => isset( $plugin['slug'] ) ? $plugin['slug'] : '', |
| 228 |
'location' => isset( $plugin['location'] ) ? $plugin['slug'].'/'.$plugin['location'] : '', |
| 229 |
'name' => isset( $plugin['name'] ) ? $plugin['name'] : '', |
| 230 |
); |
| 231 |
if( array_key_exists( $plugin['slug'], $prepare_plugin ) ){ |
| 232 |
$plugins_type = 'free'; |
| 233 |
$title = $data['name'] ? $data['name'] : $prepare_plugin[$plugin['slug']]['name']; |
| 234 |
$title = wp_kses( $title, $this->plugins_allowedtags ); |
| 235 |
$image_url = $this->plugin_icon( $plugins_type, $prepare_plugin[$data['slug']]['icons'] ); |
| 236 |
$description = wp_strip_all_tags( $prepare_plugin[$data['slug']]['description'] ); |
| 237 |
$author_name = wp_kses( $prepare_plugin[$data['slug']]['author'], $this->plugins_allowedtags ); |
| 238 |
$details_link = self_admin_url('plugin-install.php?tab=plugin-information&plugin=' . $plugin['slug'] .'&TB_iframe=true&width=772&height=577'); |
| 239 |
$target = '_self'; |
| 240 |
$modal_class = 'thickbox open-plugin-details-modal'; |
| 241 |
|
| 242 |
}else{ |
| 243 |
$plugins_type = 'pro'; |
| 244 |
$title = wp_kses( $plugin['name'], $this->plugins_allowedtags ); |
| 245 |
$image_url = $this->plugin_icon( $plugins_type, $plugin['slug'] ); |
| 246 |
$description = isset( $plugin['description'] ) ? $plugin['description'] : ''; |
| 247 |
$author_name = esc_html__( 'HasTheme', 'ever-compare' ); |
| 248 |
$author_link = isset( $plugin['author_link'] ) ? $plugin['author_link'] : ''; |
| 249 |
$details_link = isset( $plugin['link'] ) ? $plugin['link'] : ''; |
| 250 |
$button_text = esc_html__('Buy Now', 'ever-compare' ); |
| 251 |
$button_classes = 'button button-primary'; |
| 252 |
$target = '_blank'; |
| 253 |
$modal_class = ''; |
| 254 |
} |
| 255 |
|
| 256 |
if ( ! is_wp_error( $data ) ): |
| 257 |
|
| 258 |
// Installed but Inactive. |
| 259 |
if ( file_exists( WP_PLUGIN_DIR . '/' . $data['location'] ) && is_plugin_inactive( $data['location'] ) ) { |
| 260 |
|
| 261 |
$button_classes = 'button htrp-activate-now button-primary'; |
| 262 |
$button_text = esc_html__( 'Activate', 'ever-compare' ); |
| 263 |
|
| 264 |
// Not Installed. |
| 265 |
} elseif ( ! file_exists( WP_PLUGIN_DIR . '/' . $data['location'] ) ) { |
| 266 |
|
| 267 |
$button_classes = 'button htrp-install-now'; |
| 268 |
$button_text = esc_html__( 'Install Now', 'ever-compare' ); |
| 269 |
|
| 270 |
// Active. |
| 271 |
} else { |
| 272 |
$button_classes = 'button disabled'; |
| 273 |
$button_text = esc_html__( 'Activated', 'ever-compare' ); |
| 274 |
} |
| 275 |
|
| 276 |
?> |
| 277 |
<div class="plugin-card htrp-plugin-<?php echo sanitize_html_class( $plugin['slug'] ); ?>"> |
| 278 |
<div class="plugin-card-top"> |
| 279 |
<div class="name column-name" style="margin-right: 0;"> |
| 280 |
<h3> |
| 281 |
<a href="<?php echo esc_url( $details_link ) ?>" target="<?php echo esc_attr( $target ) ?>" class="<?php echo esc_attr($modal_class); ?>" > |
| 282 |
<?php echo esc_html( $title ) ?> |
| 283 |
<img src="<?php echo esc_url( $image_url ) ?>" class="plugin-icon" alt="<?php echo esc_attr( $title ) ?>"> |
| 284 |
</a> |
| 285 |
</h3> |
| 286 |
</div> |
| 287 |
<div class="desc column-description" style="margin-right: 0;"> |
| 288 |
<p><?php echo esc_html(wp_trim_words( $description, 23, '....')); ?></p> |
| 289 |
<p class="authors"> |
| 290 |
<cite><?php echo esc_html__( 'By ', 'ever-compare' ); ?> |
| 291 |
<?php if( $plugins_type == 'free' ): ?> |
| 292 |
<?php echo wp_kses_post( $author_name); ?> |
| 293 |
<?php else: ?> |
| 294 |
<a href="<?php echo esc_url( $author_link ); ?>" target="_blank" ><?php echo esc_html($author_name); ?></a> |
| 295 |
<?php endif; ?> |
| 296 |
</cite> |
| 297 |
</p> |
| 298 |
</div> |
| 299 |
</div> |
| 300 |
<div class="plugin-card-bottom"> |
| 301 |
<div class="column-updated"> |
| 302 |
<?php |
| 303 |
if (! file_exists( WP_PLUGIN_DIR . '/' . $data['location'] ) && $plugins_type == 'pro' ) { |
| 304 |
echo '<a class="button button-primary" href="'.esc_url( $details_link ).'" target="'.esc_attr( $target ).'">'.esc_html__( 'Buy Now', 'ever-compare' ).'</a>'; |
| 305 |
}else{ |
| 306 |
?> |
| 307 |
<button class="<?php echo esc_attr($button_classes); ?>" data-pluginopt='<?php echo wp_json_encode( $data ); ?>'><?php echo esc_html($button_text); ?></button> |
| 308 |
|
| 309 |
<?php } ?> |
| 310 |
</div> |
| 311 |
<div class="column-downloaded"> |
| 312 |
<a href="<?php echo esc_url( $details_link ) ?>" target="<?php echo esc_attr( $target ) ?>" class=" <?php echo esc_attr($modal_class); ?>" ><?php echo esc_html__('More Details', 'ever-compare') ?></a> |
| 313 |
<span class="downloaded-count"> |
| 314 |
<?php |
| 315 |
if( $plugins_type == 'free' ){ |
| 316 |
/* translators: %s: Number of installations. */ |
| 317 |
printf( esc_html__( '%s Active Installations', 'ever-compare' ), esc_html($this->active_install_count( $prepare_plugin[$data['slug']]['active_installs'] )) ); |
| 318 |
} |
| 319 |
?> |
| 320 |
</span> |
| 321 |
</div> |
| 322 |
</div> |
| 323 |
</div> |
| 324 |
<?php |
| 325 |
endif; |
| 326 |
endforeach; |
| 327 |
echo '</div>'; |
| 328 |
|
| 329 |
endforeach; |
| 330 |
?> |
| 331 |
|
| 332 |
</div> |
| 333 |
<?php |
| 334 |
|
| 335 |
} |
| 336 |
|
| 337 |
/** |
| 338 |
* [get_plugins_info] Look up wp.org plugin info by slug (not by author account — |
| 339 |
* wp.org's author-query filters by actual SVN repo ownership, which can differ |
| 340 |
* from a plugin's displayed "Author:" line and silently miss real plugins). |
| 341 |
* @param array $slugs Plugin slugs to look up. |
| 342 |
* @return array Associative array keyed by slug; slugs plugins_api() can't |
| 343 |
* resolve (pro-only/paid, not on wp.org) are simply absent. |
| 344 |
*/ |
| 345 |
public function get_plugins_info( $slugs ) { |
| 346 |
|
| 347 |
if ( empty( $slugs ) ) { |
| 348 |
return array(); |
| 349 |
} |
| 350 |
|
| 351 |
$slugs = array_unique( $slugs ); |
| 352 |
sort( $slugs ); // deterministic cache key regardless of tab iteration order |
| 353 |
|
| 354 |
$transient_var = 'htrp_htplugins_info_' . md5( implode( ',', $slugs ) ); |
| 355 |
$plugins_info = get_transient( $transient_var ); |
| 356 |
|
| 357 |
if ( false === $plugins_info ) { |
| 358 |
|
| 359 |
$plugins_info = array(); |
| 360 |
|
| 361 |
foreach ( $slugs as $slug ) { |
| 362 |
$plugin_info = plugins_api( 'plugin_information', array( |
| 363 |
'slug' => $slug, |
| 364 |
'fields' => array( |
| 365 |
'short_description' => true, 'sections' => false, 'icons' => true, |
| 366 |
'active_installs' => true, 'author' => true, 'versions' => false, |
| 367 |
'ratings' => false, 'reviews' => false, 'banners' => false, |
| 368 |
'compatibility' => false, 'homepage' => false, 'donate_link' => false, |
| 369 |
'tags' => false, |
| 370 |
), |
| 371 |
) ); |
| 372 |
|
| 373 |
if ( is_wp_error( $plugin_info ) ) { |
| 374 |
continue; // not on wp.org (pro-only / paid slug) — stays in the "pro" render branch |
| 375 |
} |
| 376 |
|
| 377 |
$plugins_info[ $slug ] = array( |
| 378 |
'name' => $plugin_info->name, |
| 379 |
'slug' => $plugin_info->slug, |
| 380 |
'icons' => (array) $plugin_info->icons, |
| 381 |
'description' => $plugin_info->short_description, |
| 382 |
'author' => $plugin_info->author, |
| 383 |
'active_installs' => $plugin_info->active_installs, |
| 384 |
); |
| 385 |
} |
| 386 |
|
| 387 |
set_transient( $transient_var, $plugins_info, 1 * WEEK_IN_SECONDS ); |
| 388 |
} |
| 389 |
|
| 390 |
return $plugins_info; |
| 391 |
} |
| 392 |
|
| 393 |
/** |
| 394 |
* [plugin_icon] Generate plugin icon |
| 395 |
* @param string $type plugin type |
| 396 |
* @param [array|string] $icon |
| 397 |
* @return [URL] icon URL |
| 398 |
*/ |
| 399 |
public function plugin_icon( $type = 'free', $icon = '' ){ |
| 400 |
if( $type === 'free' ){ |
| 401 |
if ( ! empty( $icon['svg'] ) ) { |
| 402 |
$plugin_icon_url = $icon['svg']; |
| 403 |
} elseif ( ! empty( $icon['2x'] ) ) { |
| 404 |
$plugin_icon_url = $icon['2x']; |
| 405 |
} elseif ( ! empty( $icon['1x'] ) ) { |
| 406 |
$plugin_icon_url = $icon['1x']; |
| 407 |
} else { |
| 408 |
$plugin_icon_url = $icon['default']; |
| 409 |
} |
| 410 |
}else{ |
| 411 |
$plugin_icon_url = $this->assets_url .'/images/extensions/'.$icon.'.png'; |
| 412 |
} |
| 413 |
|
| 414 |
return $plugin_icon_url; |
| 415 |
|
| 416 |
} |
| 417 |
|
| 418 |
/** |
| 419 |
* [active_install_count] Manage Active install count |
| 420 |
* @param [int] $active_installs |
| 421 |
* @return [string] |
| 422 |
*/ |
| 423 |
public function active_install_count( $active_installs ){ |
| 424 |
|
| 425 |
if ( $active_installs >= 1000000 ) { |
| 426 |
$active_installs_millions = floor( $active_installs / 1000000 ); |
| 427 |
$active_installs_text = sprintf( |
| 428 |
/* translators: %s: Number of millions. */ |
| 429 |
_nx( '%s+ Million', '%s+ Million', $active_installs_millions, 'Active plugin installations', 'ever-compare' ), |
| 430 |
number_format_i18n( $active_installs_millions ) |
| 431 |
); |
| 432 |
} elseif ( 0 === $active_installs ) { |
| 433 |
$active_installs_text = _x( 'Less Than 10', 'Active plugin installations', 'ever-compare' ); |
| 434 |
} else { |
| 435 |
$active_installs_text = number_format_i18n( $active_installs ) . '+'; |
| 436 |
} |
| 437 |
return $active_installs_text; |
| 438 |
|
| 439 |
} |
| 440 |
|
| 441 |
/** |
| 442 |
* [plugin_activation] Plugin activation ajax callable function |
| 443 |
* @return [JSON] |
| 444 |
*/ |
| 445 |
public function plugin_activation() { |
| 446 |
|
| 447 |
check_ajax_referer('htrp_nonce', 'nonce'); |
| 448 |
|
| 449 |
if ( ! current_user_can( 'install_plugins' ) || empty( sanitize_text_field( wp_unslash($_POST['location']) ) ) ) { |
| 450 |
wp_send_json_error( |
| 451 |
array( |
| 452 |
'success' => false, |
| 453 |
'message' => esc_html__( 'Plugin Not Found', 'ever-compare' ), |
| 454 |
) |
| 455 |
); |
| 456 |
} |
| 457 |
|
| 458 |
$plugin_location = ! empty( sanitize_text_field(wp_unslash($_POST['location'])) ) ? sanitize_text_field(wp_unslash($_POST['location'])) : ''; |
| 459 |
$activate = activate_plugin( $plugin_location, '', false, true ); |
| 460 |
|
| 461 |
if ( is_wp_error( $activate ) ) { |
| 462 |
wp_send_json_error( |
| 463 |
array( |
| 464 |
'success' => false, |
| 465 |
'message' => $activate->get_error_message(), |
| 466 |
) |
| 467 |
); |
| 468 |
} |
| 469 |
|
| 470 |
wp_send_json_success( |
| 471 |
array( |
| 472 |
'success' => true, |
| 473 |
'message' => esc_html__( 'Plugin Successfully Activated', 'ever-compare' ), |
| 474 |
) |
| 475 |
); |
| 476 |
|
| 477 |
} |
| 478 |
|
| 479 |
|
| 480 |
} |