Reviews use swiper-scroll + 3D tilt cards; feature intro stats count-up and tab video pause; tilt showcase hooks effects.js; pricing cards animate.
240 lines
8.8 KiB
PHP
240 lines
8.8 KiB
PHP
<?php
|
|
/**
|
|
* Pricing plan cards (zibll zib_widget_pricing_plans lite).
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Widgets;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class WidgetPricingPlans extends \WP_Widget {
|
|
|
|
public function __construct() {
|
|
parent::__construct(
|
|
'aksrt_pricing_plans',
|
|
esc_html__('AKSRT 价格方案', 'aksrt'),
|
|
[
|
|
'description' => esc_html__('多列定价卡片 + 信任条(落地页/VIP 介绍)。', 'aksrt'),
|
|
'classname' => 'widget_aksrt_pricing_plans',
|
|
]
|
|
);
|
|
}
|
|
|
|
public function widget($args, $instance): void {
|
|
$plans = $this->normalize_plans($instance['lists'] ?? $instance['plans'] ?? []);
|
|
if (!$plans) {
|
|
return;
|
|
}
|
|
$trust = $this->normalize_trust($instance['trust_items'] ?? []);
|
|
$currency = sanitize_text_field((string) ($instance['currency'] ?? '¥'));
|
|
if ($currency === '') {
|
|
$currency = '¥';
|
|
}
|
|
|
|
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
|
if ($title !== '') {
|
|
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
}
|
|
|
|
echo '<div class="aksrt-pricing">';
|
|
echo '<div class="aksrt-pricing__cards">';
|
|
$pi = 0;
|
|
foreach ($plans as $plan) {
|
|
$accent = sanitize_key((string) ($plan['color'] ?? 'blue'));
|
|
if (!in_array($accent, ['pink', 'purple', 'blue', 'green', 'yellow', 'cyan'], true)) {
|
|
$accent = 'blue';
|
|
}
|
|
$featured = !empty($plan['is_featured']);
|
|
$pack = function_exists('aksrt_obs_animate_attrs') ? aksrt_obs_animate_attrs('slideup', false, $pi * 0.12) : ['class' => '', 'attr' => ''];
|
|
$class = 'aksrt-pricing__card accent-' . $accent . ($featured ? ' is-featured' : '') . $pack['class'];
|
|
echo '<div class="' . esc_attr(trim($class)) . '"' . $pack['attr'] . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
$pi++;
|
|
if ($featured || !empty($plan['tag'])) {
|
|
$badge = (string) ($plan['tag'] !== '' ? $plan['tag'] : __('推荐', 'aksrt'));
|
|
echo '<span class="aksrt-pricing__badge">' . esc_html($badge) . '</span>';
|
|
}
|
|
echo '<h3 class="aksrt-pricing__name">' . esc_html($plan['name']) . '</h3>';
|
|
if ($plan['desc'] !== '') {
|
|
echo '<p class="aksrt-pricing__desc">' . esc_html($plan['desc']) . '</p>';
|
|
}
|
|
$price = (float) $plan['price'];
|
|
$show = (float) $plan['show_price'];
|
|
echo '<div class="aksrt-pricing__price">';
|
|
echo '<span class="aksrt-pricing__main"><span class="aksrt-pricing__mark">' . esc_html($currency) . '</span>' . esc_html($this->fmt($price)) . '</span>';
|
|
if ($show > $price && $show > 0) {
|
|
echo '<span class="aksrt-pricing__original"><span class="aksrt-pricing__mark">' . esc_html($currency) . '</span>' . esc_html($this->fmt($show)) . '</span>';
|
|
}
|
|
echo '</div>';
|
|
if ($plan['features']) {
|
|
echo '<ul class="aksrt-pricing__features">';
|
|
foreach ($plan['features'] as $ft) {
|
|
echo '<li><i class="fa fa-check" aria-hidden="true"></i><span>' . esc_html($ft) . '</span></li>';
|
|
}
|
|
echo '</ul>';
|
|
}
|
|
if ($plan['btn_text'] !== '' && $plan['btn_url'] !== '') {
|
|
$url = $plan['btn_url'];
|
|
if (!empty($instance['go_link']) && function_exists('aksrt_go_link')) {
|
|
$url = aksrt_go_link($url, true);
|
|
}
|
|
echo '<div class="aksrt-pricing__action"><a class="aksrt-pricing__btn" href="' . esc_url($url) . '">' . esc_html($plan['btn_text']) . '</a></div>';
|
|
}
|
|
echo '</div>';
|
|
}
|
|
echo '</div>';
|
|
|
|
if ($trust) {
|
|
echo '<div class="aksrt-pricing__trust">';
|
|
foreach ($trust as $ti) {
|
|
echo '<div class="aksrt-pricing__trust-item">';
|
|
if ($ti['icon'] !== '') {
|
|
echo '<i class="' . esc_attr($ti['icon']) . '" aria-hidden="true"></i>';
|
|
}
|
|
echo '<div><strong>' . esc_html($ti['title']) . '</strong>';
|
|
if ($ti['desc'] !== '') {
|
|
echo '<span>' . esc_html($ti['desc']) . '</span>';
|
|
}
|
|
echo '</div></div>';
|
|
}
|
|
echo '</div>';
|
|
}
|
|
echo '</div>';
|
|
|
|
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
}
|
|
|
|
private function fmt(float $n): string {
|
|
if (abs($n - round($n)) < 0.001) {
|
|
return (string) (int) round($n);
|
|
}
|
|
return number_format($n, 2, '.', '');
|
|
}
|
|
|
|
/**
|
|
* @param mixed $raw
|
|
* @return array<int, array<string,mixed>>
|
|
*/
|
|
private function normalize_plans($raw): array {
|
|
if (is_string($raw)) {
|
|
$decoded = json_decode($raw, true);
|
|
$raw = is_array($decoded) ? $decoded : [];
|
|
}
|
|
if (!is_array($raw)) {
|
|
return [];
|
|
}
|
|
$out = [];
|
|
foreach ($raw as $row) {
|
|
if (!is_array($row)) {
|
|
continue;
|
|
}
|
|
$name = sanitize_text_field((string) ($row['name'] ?? $row['title'] ?? ''));
|
|
if ($name === '') {
|
|
continue;
|
|
}
|
|
$intro = (string) ($row['intro'] ?? $row['features'] ?? '');
|
|
if (is_array($row['features'] ?? null)) {
|
|
$features = array_values(array_filter(array_map('sanitize_text_field', $row['features'])));
|
|
} else {
|
|
$features = array_values(array_filter(array_map('sanitize_text_field', preg_split('/\r\n|\r|\n/', $intro) ?: [])));
|
|
}
|
|
$btn = is_array($row['btn'] ?? null) ? $row['btn'] : [];
|
|
$out[] = [
|
|
'name' => $name,
|
|
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
|
|
'price' => (float) ($row['price'] ?? 0),
|
|
'show_price' => (float) ($row['show_price'] ?? 0),
|
|
'color' => sanitize_key((string) ($row['color'] ?? 'blue')),
|
|
'is_featured' => !empty($row['is_featured']) ? 1 : 0,
|
|
'tag' => sanitize_text_field((string) ($row['tag'] ?? '')),
|
|
'features' => $features,
|
|
'btn_text' => sanitize_text_field((string) ($row['btn_text'] ?? $btn['text'] ?? '')),
|
|
'btn_url' => esc_url_raw(trim((string) ($row['btn_url'] ?? $btn['url'] ?? ''))),
|
|
];
|
|
if (count($out) >= 6) {
|
|
break;
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $raw
|
|
* @return array<int, array{title:string,desc:string,icon:string}>
|
|
*/
|
|
private function normalize_trust($raw): array {
|
|
if (is_string($raw)) {
|
|
$decoded = json_decode($raw, true);
|
|
$raw = is_array($decoded) ? $decoded : [];
|
|
}
|
|
if (!is_array($raw)) {
|
|
return [];
|
|
}
|
|
$out = [];
|
|
foreach ($raw as $row) {
|
|
if (!is_array($row)) {
|
|
continue;
|
|
}
|
|
$title = sanitize_text_field((string) ($row['title'] ?? ''));
|
|
if ($title === '') {
|
|
continue;
|
|
}
|
|
$out[] = [
|
|
'title' => $title,
|
|
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
|
|
'icon' => sanitize_text_field((string) ($row['icon'] ?? 'fa fa-check-circle')),
|
|
];
|
|
if (count($out) >= 6) {
|
|
break;
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
public function form($instance): void {
|
|
$plans = is_string($instance['lists'] ?? null)
|
|
? (string) $instance['lists']
|
|
: wp_json_encode($this->normalize_plans($instance['lists'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
$trust = is_string($instance['trust_items'] ?? null)
|
|
? (string) $instance['trust_items']
|
|
: wp_json_encode($this->normalize_trust($instance['trust_items'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
?>
|
|
<p>
|
|
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
|
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
|
</label>
|
|
</p>
|
|
<p>
|
|
<label><?php esc_html_e('货币符号', 'aksrt'); ?>
|
|
<input class="tiny-text" type="text" name="<?php echo esc_attr($this->get_field_name('currency')); ?>" value="<?php echo esc_attr((string) ($instance['currency'] ?? '¥')); ?>">
|
|
</label>
|
|
</p>
|
|
<p>
|
|
<label><?php esc_html_e('方案 JSON', 'aksrt'); ?>
|
|
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('lists')); ?>" placeholder='[{"name":"Pro","price":99,"show_price":199,"desc":"适合创作者","features":["无限下载","优先支持"],"btn_text":"立即开通","btn_url":"#","is_featured":1,"color":"pink"}]'><?php echo esc_textarea((string) $plans); ?></textarea>
|
|
</label>
|
|
</p>
|
|
<p>
|
|
<label><?php esc_html_e('信任条 JSON', 'aksrt'); ?>
|
|
<textarea class="widefat" rows="3" name="<?php echo esc_attr($this->get_field_name('trust_items')); ?>"><?php echo esc_textarea((string) $trust); ?></textarea>
|
|
</label>
|
|
</p>
|
|
<p>
|
|
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
|
|
</p>
|
|
<?php
|
|
}
|
|
|
|
public function update($new_instance, $old_instance): array {
|
|
return [
|
|
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
|
'currency' => sanitize_text_field((string) ($new_instance['currency'] ?? '¥')),
|
|
'lists' => $this->normalize_plans($new_instance['lists'] ?? []),
|
|
'trust_items' => $this->normalize_trust($new_instance['trust_items'] ?? []),
|
|
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
|
];
|
|
}
|
|
}
|