feat(pages): 1:1 网址导航 page-links (zibll links.php)

Add WP bookmarks-based navigation page with multi-category sticky nav,
search-engine cover, front submit modal/AJAX, page meta, and styles.
This commit is contained in:
2026-07-18 21:02:23 +08:00
parent fc9278e0ab
commit c27a36faff
11 changed files with 1707 additions and 4 deletions
+1
View File
@@ -43,6 +43,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-coupon-order.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-credits.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-null.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-ajax-list.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-links.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php';
require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php';
+3 -4
View File
@@ -7,12 +7,11 @@ defined('ABSPATH') || exit;
add_filter('show_admin_bar', '__return_false');
// ============================================================
// 隐藏不需要的默认菜单项
// WordPress 链接管理(网址导航 / 书签)
// zibll enables link_manager; AKSRT keeps it for page-links.
// ============================================================
add_action('admin_menu', function () {
remove_menu_page('link-manager.php');
});
add_filter('pre_option_link_manager_enabled', '__return_true');
// ============================================================
// Gravatar 头像源替换(4层拦截:get_avatar_url / pre_get_avatar_data / avatar_defaults / ob_start 兜底)
+486
View File
@@ -0,0 +1,486 @@
<?php
/**
* 网址导航 / 书签链接渲染(zibll pages/links.php + zib_links_box parity).
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_go_link')) :
/**
* Wrap external URL with /go/ intermediate page when enabled.
*/
function aksrt_go_link(string $url, bool $force = false): string {
$url = esc_url_raw($url);
if ($url === '') {
return '';
}
if (!$force && class_exists('\AKSRT\Settings\Settings') && empty(\AKSRT\Settings\Settings::get_value('seo_go_link', true))) {
return $url;
}
if (class_exists('\AKSRT\Core\GoRedirect')) {
return \AKSRT\Core\GoRedirect::get_instance()->create_go_link($url);
}
return $url;
}
endif;
if (!function_exists('aksrt_get_page_links_settings')) :
/**
* Read page_links_* meta for a page.
*
* @return array<string,mixed>
*/
function aksrt_get_page_links_settings(int $post_id = 0): array {
if ($post_id <= 0) {
$post_id = (int) get_queried_object_id();
}
$cats = get_post_meta($post_id, 'page_links_category', true);
if (!is_array($cats)) {
$cats = $cats !== '' && $cats !== null ? array_filter(array_map('intval', (array) $cats)) : [];
} else {
$cats = array_values(array_filter(array_map('intval', $cats)));
}
$search_types = get_post_meta($post_id, 'page_links_search_types', true);
if (!is_array($search_types)) {
$search_types = $search_types ? array_values(array_filter(array_map('sanitize_key', (array) $search_types))) : [];
} else {
$search_types = array_values(array_filter(array_map('sanitize_key', $search_types)));
}
$submit_cats = get_post_meta($post_id, 'page_links_submit_cats', true);
if (!is_array($submit_cats)) {
$submit_cats = $submit_cats !== '' && $submit_cats !== null ? array_filter(array_map('intval', (array) $submit_cats)) : [];
} else {
$submit_cats = array_values(array_filter(array_map('intval', $submit_cats)));
}
$style = sanitize_key((string) get_post_meta($post_id, 'page_links_style', true));
if (!in_array($style, ['card', 'bigcard', 'image', 'simple'], true)) {
$style = 'card';
}
$orderby = sanitize_key((string) get_post_meta($post_id, 'page_links_orderby', true));
if (!in_array($orderby, ['name', 'updated', 'rating', 'rand', 'id', 'length'], true)) {
$orderby = 'name';
}
$order = strtoupper((string) get_post_meta($post_id, 'page_links_order', true));
if (!in_array($order, ['ASC', 'DESC'], true)) {
$order = 'ASC';
}
$position = sanitize_key((string) get_post_meta($post_id, 'page_links_content_position', true));
if (!in_array($position, ['top', 'bottom'], true)) {
$position = 'top';
}
return [
'content_s' => (bool) get_post_meta($post_id, 'page_links_content_s', true),
'content_position' => $position,
'search_s' => (bool) get_post_meta($post_id, 'page_links_search_s', true),
'search_types' => $search_types,
'category' => $cats,
'style' => $style,
'limit' => max(0, (int) get_post_meta($post_id, 'page_links_limit', true)),
'orderby' => $orderby,
'order' => $order,
'go_s' => (bool) get_post_meta($post_id, 'page_links_go_s', true),
'blank_s' => (bool) get_post_meta($post_id, 'page_links_blank_s', true),
// Default nofollow ON (zibll default true).
'nofollow_s' => get_post_meta($post_id, 'page_links_nofollow_s', true) === ''
? true
: (bool) get_post_meta($post_id, 'page_links_nofollow_s', true),
'submit_s' => (bool) get_post_meta($post_id, 'page_links_submit_s', true),
'submit_sign_s' => get_post_meta($post_id, 'page_links_submit_sign_s', true) === ''
? true
: (bool) get_post_meta($post_id, 'page_links_submit_sign_s', true),
'submit_cats' => $submit_cats,
'submit_title' => (string) (get_post_meta($post_id, 'page_links_submit_title', true) ?: __('申请入驻', 'aksrt')),
'submit_dec' => (string) get_post_meta($post_id, 'page_links_submit_dec', true),
];
}
endif;
if (!function_exists('aksrt_links_box')) :
/**
* Render bookmark cards (zibll zib_links_box).
*
* @param array<int,object|array> $links Bookmark rows from get_bookmarks().
*/
function aksrt_links_box($links = [], string $type = 'card', bool $nofollow = true, bool $go_link = false, bool $blank_s = false): string {
if (empty($links) || !is_array($links)) {
return '';
}
$type = in_array($type, ['card', 'bigcard', 'image', 'simple'], true) ? $type : 'card';
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
$html = '';
$i = 0;
foreach ($links as $raw) {
$link = (array) $raw;
if (empty($link['href']) && !empty($link['link_url'])) {
$link['href'] = $link['link_url'];
}
if (empty($link['title']) && !empty($link['link_name'])) {
$link['title'] = $link['link_name'];
}
if (empty($link['src']) && !empty($link['link_image'])) {
$link['src'] = $link['link_image'];
}
if (empty($link['desc']) && !empty($link['link_description'])) {
$link['desc'] = $link['link_description'];
}
if ($blank_s) {
$link['blank'] = true;
}
if (empty($link['blank']) && !empty($link['link_target'])) {
$link['blank'] = true;
}
$href = !empty($link['href']) ? (string) $link['href'] : '';
$title = !empty($link['title']) ? (string) $link['title'] : '';
if ($href === '' || $title === '') {
continue;
}
if ($go_link || !empty($link['go_link'])) {
$href = aksrt_go_link($href, true);
} else {
$href = esc_url($href);
}
$src = !empty($link['src']) ? esc_url((string) $link['src']) : '';
$blank = !empty($link['blank']) ? ' target="_blank"' : '';
$dec = !empty($link['desc']) ? (string) $link['desc'] : '';
$rel = $nofollow ? ' rel="nofollow noopener noreferrer"' : ' rel="noopener noreferrer"';
$img_src = $src !== '' ? $src : $placeholder;
$img = '<img class="lazyload avatar" src="' . esc_url($placeholder) . '" data-src="' . esc_url($img_src) . '" alt="" loading="lazy">';
$data_dec = $dec !== ''
? ' title="' . esc_attr($title) . '" data-content="' . esc_attr($dec) . '"'
: ' data-content="' . esc_attr($title) . '"';
if ($type === 'bigcard') {
$html .= '<div class="author-minicard links-card radius8 big-card">';
$html .= '<a' . $blank . ' href="' . esc_url($href) . '" title="' . esc_attr($dec) . '"' . $rel . '>';
$html .= '<div class="avatar-img link-img">' . $img . '</div>';
$html .= '<div class="text-ellipsis mt20 font-bold">' . esc_html($title) . '</div>';
if ($dec !== '') {
$html .= '<dd class="mt6 avatar-dest em09 muted-3-color text-ellipsis">' . esc_html($dec) . '</dd>';
}
$html .= '</a></div>';
} elseif ($type === 'image') {
$html .= '<a' . $blank . ' class="avatar-img link-img link-only-img" data-trigger="hover" data-toggle="popover" data-placement="top"'
. $data_dec . ' href="' . esc_url($href) . '"' . $rel . '>' . $img . '</a>';
} elseif ($type === 'simple') {
$sc = $i === 0 ? '' : 'icon-spot';
$html .= '<a' . $blank . ' class="' . esc_attr($sc) . '" data-trigger="hover" data-toggle="popover" data-placement="top"'
. $data_dec . ' href="' . esc_url($href) . '"' . $rel . '>' . esc_html($title) . '</a>';
} else {
$html .= '<div class="author-minicard links-card radius8"><a' . $blank . ' href="' . esc_url($href) . '" title="' . esc_attr($dec) . '"' . $rel . '>';
$html .= '<ul class="list-inline">';
$html .= '<li><div class="avatar-img link-img">' . $img . '</div></li>';
$html .= '<li><dl><dt class="text-ellipsis">' . esc_html($title) . '</dt>';
if ($dec !== '') {
$html .= '<dd class="mt3 avatar-dest em09 muted-3-color text-ellipsis">' . esc_html($dec) . '</dd>';
}
$html .= '</dl></li></ul></a></div>';
}
$i++;
}
return $html;
}
endif;
if (!function_exists('aksrt_page_links_html')) :
/**
* Build the multi/single category links block for a page.
*/
function aksrt_page_links_html(int $post_id = 0): string {
$post_id = $post_id > 0 ? $post_id : (int) get_queried_object_id();
$s = aksrt_get_page_links_settings($post_id);
$cats = $s['category'];
if (empty($cats)) {
if (current_user_can('manage_links')) {
return '<div class="mb20"><a class="author-minicard links-card radius8" href="' . esc_url(admin_url('link-manager.php')) . '" target="_blank" rel="noopener">'
. esc_html__('添加链接', 'aksrt') . '</a></div>';
}
return '<div class="author-minicard links-card radius8 mb20">' . esc_html__('暂无链接', 'aksrt') . '</div>';
}
$limit = (int) $s['limit'];
$orderby = (string) $s['orderby'];
$order = (string) $s['order'];
$style = (string) $s['style'];
$nofollow = (bool) $s['nofollow_s'];
$go_s = (bool) $s['go_s'];
$blank_s = (bool) $s['blank_s'];
$submit_s = (bool) $s['submit_s'];
$submit_title = (string) $s['submit_title'];
$is_mobile = wp_is_mobile();
$links_con = '';
if (count($cats) > 1) {
$link_cat_con = '';
$link_cat_nav = '';
foreach ($cats as $cat_id) {
$args = [
'orderby' => $orderby,
'order' => $order,
'limit' => $limit > 0 ? $limit : -1,
'category' => (int) $cat_id,
];
$_link = aksrt_links_box(get_bookmarks($args), $style, $nofollow, $go_s, $blank_s);
if (!$_link) {
continue;
}
$term = get_term((int) $cat_id, 'link_category');
if (!$term || is_wp_error($term)) {
continue;
}
$_id = 'links-' . (int) $cat_id;
$link_cat_con .= '<div class="mb20 links-cat-box"><div class="links-cat-header"><div class="title-theme"><div id="' . esc_attr($_id) . '">'
. esc_html($term->name) . '</div></div></div><div class="links-box links-style-' . esc_attr($style) . '">' . $_link . '</div></div>';
$link_cat_nav .= '<li class="n-H2"><a class="text-ellipsis" href="#' . esc_attr($_id) . '">' . esc_html($term->name) . '</a></li>';
}
if ($link_cat_con === '') {
$links_con = '';
} else {
$submit_nav = $submit_s
? '<div class="link-nav-submit"><a class="padding-h10 hollow but c-theme btn-block text-ellipsis" href="#submit-links-modal" data-toggle="modal">'
. esc_html($submit_title) . '</a></div>'
: '';
$submit_con = $submit_s
? '<div class="link-cat-submit"><a class="padding-h10 hollow but c-theme btn-block text-ellipsis" href="#submit-links-modal" data-toggle="modal">'
. esc_html($submit_title) . '</a></div>'
: '';
$offset_top = $is_mobile ? '-8' : '';
$offset_bottom = $is_mobile ? '20' : '-30';
$links_con = '<div class="fixed-wrap">'
. '<div class="fixed-wrap-nav affix-header-sm affix link-nav-box tab-auto-center-sm" data-offset-top="' . esc_attr((string) $offset_top) . '" data-offset-bottom="' . esc_attr((string) $offset_bottom) . '">'
. '<div class="zib-widget"><ul class="nav list-inline scroll-x mini-scrollbar tab-nav-theme">' . $link_cat_nav . '</ul></div>'
. $submit_nav
. '</div><div class="fixed-wrap-content">' . $link_cat_con . $submit_con . '</div></div>';
}
} else {
$cat_id = (int) $cats[0];
$args = [
'orderby' => $orderby,
'order' => $order,
'limit' => $limit > 0 ? $limit : -1,
'category' => $cat_id,
];
$submit_con = $submit_s
? '<div class="mt20"><a class="padding-h10 hollow but c-theme btn-block text-ellipsis" href="#submit-links-modal" data-toggle="modal">'
. esc_html($submit_title) . '</a></div>'
: '';
$links_con = '<div class="links-box links-style-' . esc_attr($style) . '">'
. aksrt_links_box(get_bookmarks($args), $style, $nofollow, $go_s, $blank_s)
. '</div>' . $submit_con;
}
if ($links_con === '') {
if (current_user_can('manage_links')) {
return '<div class="mb20"><a class="author-minicard links-card radius8" href="' . esc_url(admin_url('link-manager.php')) . '" target="_blank" rel="noopener">'
. esc_html__('添加链接', 'aksrt') . '</a></div>';
}
return '<div class="author-minicard links-card radius8 mb20">' . esc_html__('暂无链接', 'aksrt') . '</div>';
}
return '<div class="links-page-container mb20">' . $links_con . '</div>';
}
endif;
if (!function_exists('aksrt_link_get_search_cover')) :
/**
* Search cover with multi-engine tabs (zibll zib_link_get_search_cover).
*/
function aksrt_link_get_search_cover(int $post_id = 0): string {
$post_id = $post_id > 0 ? $post_id : (int) get_queried_object_id();
$s = aksrt_get_page_links_settings($post_id);
if (empty($s['search_s']) || empty($s['search_types'])) {
return '';
}
$search_types_args = [
'self' => [
'name' => __('站内', 'aksrt'),
'input_name' => 's',
'url' => home_url('/'),
],
'baidu' => [
'name' => __('百度', 'aksrt'),
'input_name' => 'wd',
'url' => 'https://www.baidu.com/s',
],
'google' => [
'name' => __('谷歌', 'aksrt'),
'input_name' => 'q',
'url' => 'https://www.google.com/search',
],
'bing' => [
'name' => __('必应', 'aksrt'),
'input_name' => 'q',
'url' => 'https://cn.bing.com/search',
],
'sogou' => [
'name' => __('搜狗', 'aksrt'),
'input_name' => 'query',
'url' => 'https://www.sogou.com/web',
],
'360' => [
'name' => '360',
'input_name' => 'q',
'url' => 'https://www.so.com/s',
],
];
$type_nav = '';
$type_con = '';
$i = 0;
foreach ($s['search_types'] as $k) {
if (!isset($search_types_args[$k])) {
continue;
}
$cfg = $search_types_args[$k];
$active = $i === 0 ? ' active' : '';
$pane_id = 'link-page-' . $post_id . '-' . $k;
$type_nav .= '<li class="' . esc_attr(trim($active)) . '"><a href="javascript:;" data-aksrt-tab-target="#' . esc_attr($pane_id) . '">'
. esc_html($cfg['name']) . '</a></li>';
$type_con .= '<div class="tab-pane' . ($i === 0 ? ' in active' : '') . '" id="' . esc_attr($pane_id) . '">'
. '<form method="get" target="_blank" class="padding-10 search-form" action="' . esc_url($cfg['url']) . '">'
. '<div class="line-form blur-bg"><div class="search-input-text">'
. '<input type="text" name="' . esc_attr($cfg['input_name']) . '" class="line-form-input" tabindex="1" value="" autocomplete="off">'
. '<i class="line-form-line"></i>'
. '<div class="scale-placeholder">' . esc_html(sprintf(__('%s搜索', 'aksrt'), $cfg['name'])) . '</div>'
. '<div class="abs-right muted-color"><button type="submit" tabindex="2" class="null" aria-label="' . esc_attr__('搜索', 'aksrt') . '">'
. '<i class="fa fa-search" aria-hidden="true"></i></button></div>'
. '</div></div></form></div>';
$i++;
}
if ($type_nav === '') {
return '';
}
$title = get_the_title($post_id);
$img = '';
$thumb_id = get_post_thumbnail_id($post_id);
if ($thumb_id) {
$image = wp_get_attachment_image_src($thumb_id, 'full');
$img = !empty($image[0]) ? (string) $image[0] : '';
}
if ($img === '') {
$img = (string) get_post_meta($post_id, 'page_header_cover_img', true);
}
if ($img === '' && class_exists('\AKSRT\Settings\Settings')) {
$img = (string) \AKSRT\Settings\Settings::get_value('page_header_cover_img', '');
}
if ($img === '') {
$img = get_template_directory_uri() . '/assets/img/placeholder.svg';
}
$src = get_template_directory_uri() . '/assets/img/placeholder.svg';
$search = '<div class="header-slider-search abs-center"><div class="header-slider-search-more text-center before">'
. '<div class="em14 font-bold mb10">' . esc_html($title) . '</div></div>'
. '<div class="search-input"><div class="flex jc"><ul class="list-inline scroll-x mini-scrollbar tab-nav-theme">' . $type_nav . '</ul></div>'
. '<div class="tab-content">' . $type_con . '</div></div></div>';
return '<div class="page-cover theme-box radius8 main-shadow link-page-search-cover">'
. '<img class="fit-cover no-scale lazyload" src="' . esc_url($src) . '" data-src="' . esc_url($img) . '" alt="">'
. $search . '</div>';
}
endif;
if (!function_exists('aksrt_submit_links_modal')) :
/**
* Frontend submit modal markup (zibll zib_submit_links_modal).
*
* @param array{title?:string,dec?:string,sign?:bool,cats?:int[]} $args
*/
function aksrt_submit_links_modal(array $args = []): string {
$defaults = [
'title' => __('申请入驻', 'aksrt'),
'dec' => '',
'sign' => true,
'cats' => [],
];
$args = array_merge($defaults, $args);
$title = (string) $args['title'];
$header = $title !== ''
? '<div class="mb20"><button type="button" class="close" data-dismiss="modal" aria-label="' . esc_attr__('关闭', 'aksrt') . '">'
. '<i class="fa fa-times" aria-hidden="true"></i></button>'
. '<b class="modal-title flex ac"><span class="toggle-radius mr10 b-theme"><i class="fa fa-pencil-square-o" aria-hidden="true"></i></span>'
. esc_html($title) . '</b></div>'
: '';
$body = '';
if (!empty($args['dec'])) {
$body .= '<div class="muted-box em09">' . wp_kses_post((string) $args['dec']) . '</div>';
}
$need_login = !empty($args['sign']) && !is_user_logged_in();
if ($need_login) {
$sign_url = function_exists('aksrt_get_login_url')
? aksrt_get_login_url()
: (function_exists('aksrt_get_sign_page_url') ? aksrt_get_sign_page_url('login') : wp_login_url());
$body .= '<div class="muted-box text-center">';
$body .= '<div class="mb20 muted-3-color">' . esc_html__('请先登录', 'aksrt') . '</div>';
$body .= '<p><a href="' . esc_url($sign_url) . '" class="signin-loader but c-blue padding-lg">'
. '<i class="fa fa-fw fa-sign-in mr10" aria-hidden="true"></i>' . esc_html__('登录', 'aksrt') . '</a></p>';
if (class_exists('\AKSRT\Auth\OAuth') && method_exists(\AKSRT\Auth\OAuth::get_instance(), 'render_social_buttons')) {
$social = (string) \AKSRT\Auth\OAuth::get_instance()->render_social_buttons(__('使用以下方式快速登录', 'aksrt'));
if ($social !== '') {
$body .= '<div class="social_loginbar">' . $social . '</div>';
}
}
$body .= '</div>';
} else {
$term_args = [
'taxonomy' => 'link_category',
'hide_empty' => false,
];
if (!empty($args['cats']) && is_array($args['cats'])) {
$term_args['include'] = array_map('intval', $args['cats']);
$term_args['orderby'] = 'include';
}
$terms = get_terms($term_args);
$cats_options = '';
if (!is_wp_error($terms) && !empty($terms)) {
foreach ($terms as $item) {
$cats_options .= '<option value="' . (int) $item->term_id . '">' . esc_html($item->name) . '</option>';
}
}
$cats_field = $cats_options !== ''
? '<div class="col-sm-12 mb10"><div class="em09 muted-2-color mb6">' . esc_html__('网站类别', 'aksrt') . '</div>'
. '<div class="form-select"><select name="link_category" class="form-control">' . $cats_options . '</select></div></div>'
: '';
$placeholder_upload = get_template_directory_uri() . '/assets/img/placeholder.svg';
$body .= '<form class="form-horizontal mt10 form-upload aksrt-links-submit-form" enctype="multipart/form-data">';
$body .= '<div class="row gutters-5">';
$body .= '<div class="col-sm-6 mb10"><div class="em09 muted-2-color mb6">' . esc_html__('网站名称(必填)', 'aksrt') . '</div>'
. '<input type="text" class="form-control" name="link_name" required placeholder="' . esc_attr__('请输入网站名称', 'aksrt') . '"></div>';
$body .= '<div class="col-sm-6 mb10"><div class="em09 muted-2-color mb6">' . esc_html__('网站地址(必填)', 'aksrt') . '</div>'
. '<input type="url" class="form-control" name="link_url" required placeholder="https://..."></div>';
$body .= '<div class="col-sm-12 mb10"><div class="em09 muted-2-color mb6">' . esc_html__('网站简介', 'aksrt') . '</div>'
. '<input type="text" class="form-control" name="link_description" placeholder="' . esc_attr__('一句话介绍网站', 'aksrt') . '"></div>';
$body .= $cats_field;
$body .= '<div class="col-sm-12 mb10"><div class="em09 muted-2-color mb6">' . esc_html__('LOGO图像', 'aksrt') . '</div>'
. '<label class="pointer"><div class="preview preview-square"><img class="fit-cover aksrt-links-logo-preview" src="'
. esc_url($placeholder_upload) . '" alt=""></div>'
. '<input class="hide aksrt-links-logo-input" type="file" accept="image/gif,image/jpeg,image/jpg,image/png" name="image"></label>'
. '<div class="px12 muted-2-color mb6">' . esc_html__('请选择正方形 LOGO,支持 jpg/png/gif', 'aksrt') . '</div></div>';
$body .= '</div>';
$body .= '<div class="text-right edit-footer">'
. '<button type="submit" class="but c-blue padding-lg aksrt-links-submit-btn"><i class="fa fa-check" aria-hidden="true"></i>'
. esc_html__('确认提交', 'aksrt') . '</button></div>';
$body .= wp_nonce_field('aksrt_frontend_links_submit', '_wpnonce', false, false);
$body .= '<input type="hidden" name="action" value="aksrt_frontend_links_submit">';
$body .= '</form>';
}
return '<div class="modal fade" id="submit-links-modal" tabindex="-1" role="dialog" aria-hidden="true">'
. '<div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-body">'
. $header . $body
. '</div></div></div></div>';
}
endif;