Plugin Directory

Changeset 3454739


Ignore:
Timestamp:
02/05/2026 03:10:11 PM (8 weeks ago)
Author:
denishua
Message:

version 6.9.2

Location:
wpjam-basic/trunk
Files:
28 edited

Legend:

Unmodified
Added
Removed
  • wpjam-basic/trunk/components/server-status.php

    r3440469 r3454739  
    1111
    1212class WPJAM_Server_Status{
    13     public static function server_widget(){
    14         $items[]    = ['title'=>'服务器',      'value'=>gethostname().'('.$_SERVER['HTTP_HOST'].')'];
    15         $items[]    = ['title'=>'服务器IP',    'value'=>'内网:'.gethostbyname(gethostname())];
    16         $items[]    = ['title'=>'系统',       'value'=>php_uname('s')];
    17 
    18         if(strpos(ini_get('open_basedir'), ':/proc') !== false){
    19             if(@is_readable('/proc/cpuinfo')){
    20                 $cpus   = wpjam_lines(file_get_contents('/proc/cpuinfo'), "\n\n");
    21                 $base[] = count($cpus).'核';
     13    public static function callback(...$args){
     14        $id     = $args[1]['id'];
     15        $items  = [self::class, $GLOBALS['current_tab']]($id);
     16
     17        if($id == 'usage'){
     18            foreach($items as $item){
     19                echo '<hr />';
     20
     21                $unit   = wpjam_pull($item[1], 'unit') ?: 1;
     22                $data   = wpjam_map(wpjam_pull($item[1], 'labels'), fn($v, $k) => ['label'=>$v, 'count'=>round($item[0][$k]/$unit, 2)]);
     23
     24                echo wpjam_chart('donut', $data, $item[1]+['total'=>true, 'chart_width'=>150, 'table_width'=>320]);
    2225            }
     26        }else{
     27            ?>
     28            <table class="widefat striped" style="border:none;">
     29                <tbody><?php foreach($items as $item){ ?>
     30                    <tr><?php foreach($item as $i){ ?>
     31                        <td><?php echo $i; ?></td>
     32                    <?php } ?></tr>
     33                <?php } ?></tbody>
     34            </table>
     35            <?php
     36        }
     37    }
     38
     39    public static function server($id){
     40        if($id == 'server'){
     41            $items[]    = ['服务器',       gethostname().'('.$_SERVER['HTTP_HOST'].')'];
     42            $items[]    = ['服务器IP', '内网:'.gethostbyname(gethostname())];
     43            $items[]    = ['系统',        php_uname('s')];
     44
     45            if(strpos(ini_get('open_basedir'), ':/proc') !== false){
     46                if(@is_readable('/proc/cpuinfo')){
     47                    $cpus   = wpjam_lines(file_get_contents('/proc/cpuinfo'), "\n\n");
     48                    $base[] = count($cpus).'核';
     49                }
     50               
     51                if(@is_readable('/proc/meminfo')){
     52                    $mems   = wpjam_lines(file_get_contents('/proc/meminfo'));
     53                    $mem    = (int)substr(array_find($mems, fn($m) => str_starts_with($m, 'MemTotal:')), 9);
     54                    $base[] = round($mem/1024/1024).'G';
     55                }
     56
     57                if(!empty($base)){
     58                    $items[]    = ['配置',    '<strong>'.implode('&nbsp;/&nbsp;', $base).'</strong>'];
     59                }
    2360           
    24             if(@is_readable('/proc/meminfo')){
    25                 $mems   = wpjam_lines(file_get_contents('/proc/meminfo'));
    26                 $mem    = (int)substr(array_find($mems, fn($m) => str_starts_with($m, 'MemTotal:')), 9);
    27                 $base[] = round($mem/1024/1024).'G';
     61                if(@is_readable('/proc/meminfo')){
     62                    $uptime     = wpjam_lines(file_get_contents('/proc/uptime'), ' ');
     63                    $items[]    = ['运行时间',  human_time_diff(time()-$uptime[0])];
     64                }
     65
     66               
     67                $items[]    = ['空闲率',       round($uptime[1]*100/($uptime[0]*count($cpus)), 2).'%'];
     68                $items[]    = ['系统负载',  '<strong>'.implode('&nbsp;&nbsp;',sys_getloadavg()).'</strong>'];
    2869            }
    2970
    30             if(!empty($base)){
    31                 $items[]    = ['title'=>'配置',   'value'=>'<strong>'.implode('&nbsp;/&nbsp;', $base).'</strong>'];
     71            $items[]    = ['文档根目录', $_SERVER['DOCUMENT_ROOT']];
     72           
     73            return $items;
     74        }elseif($id == 'version'){
     75            return [
     76                [wpjam_lines($_SERVER['SERVER_SOFTWARE'], '/')[0],  $_SERVER['SERVER_SOFTWARE']],
     77                ['MySQL',       $GLOBALS['wpdb']->db_version().'(最低要求:'.$GLOBALS['required_mysql_version'].')'],
     78                ['PHP',         phpversion().'(最低要求:'.$GLOBALS['required_php_version'].')'],
     79                ['Zend',        Zend_Version()],
     80                ['WordPress',   $GLOBALS['wp_version'].'('.$GLOBALS['wp_db_version'].')'],
     81                ['TinyMCE',     $GLOBALS['tinymce_version']]
     82            ];
     83        }elseif($id == 'php'){
     84            return [[implode('&emsp;', get_loaded_extensions())]];
     85        }elseif($id == 'apache'){
     86            return [[implode('&emsp;', apache_get_modules())]];
     87        }
     88    }
     89
     90    public static function opcache($id){
     91        if($id == 'usage'){
     92            echo '<p>'.wpjam_get_page_button('reset_opcache').'</p>';
     93
     94            $status = opcache_get_status();
     95            $rest   = $status['opcache_statistics']['max_cached_keys']-$status['opcache_statistics']['num_cached_keys'];
     96
     97            return [
     98                [$status['memory_usage'], [
     99                    'title'     => '内存使用',
     100                    'labels'    => ['used_memory'=>'已用内存', 'free_memory'=>'剩余内存', 'wasted_memory'=>'浪费内存'],
     101                    'unit'      => 1024*1024
     102                ]],
     103                [$status['opcache_statistics'], [
     104                    'title'     => '命中率',
     105                    'labels'    => ['hits'=>'命中', 'misses'=>'未命中']
     106                ]],
     107                [$status['opcache_statistics']+['rest_cached_keys'=>$rest], [
     108                    'title'     => '存储Keys',
     109                    'labels'    => ['num_cached_keys'=>'已用Keys', 'rest_cached_keys'=>'剩余Keys']
     110                ]]
     111            ];
     112        }elseif($id == 'status'){
     113            return wpjam_entries(opcache_get_status()['opcache_statistics']);
     114        }elseif($id == 'configuration'){
     115            $config = opcache_get_configuration();
     116       
     117            return array_reduce([$config['version'], wpjam_array($config['directives'], fn($k)=> str_replace('opcache.', '', $k))], fn($c, $v)=> array_merge($c, wpjam_entries($v)), []);
     118        }
     119    }
     120
     121    public static function memcached($id){
     122        foreach($GLOBALS['wp_object_cache']->get_stats() as $key => $stats){
     123            if($id == 'usage'){
     124                echo '<p>'.wpjam_get_page_button('flush_mc').'</p>';
     125
     126                return [
     127                    [$stats, [
     128                        'title'     => '命中率',
     129                        'labels'    => ['get_hits'=>'命中次数', 'get_misses'=>'未命中次数'],
     130                    ]],
     131                    [$stats+['rest'=>$stats['limit_maxbytes']-$stats['bytes']], [
     132                        'title'     => '内存使用',
     133                        'labels'    => ['bytes'=>'已用内存', 'rest'=>'剩余内存'],
     134                        'unit'      => 1024*1024
     135                    ]]
     136                ];
     137            }elseif($id == 'status'){
     138                return [
     139                    ['Memcached地址', $key],
     140                    ['Memcached版本', $stats['version']],
     141                    ['进程ID',            $stats['pid']],
     142                    ['启动时间',            wpjam_date('Y-m-d H:i:s',($stats['time']-$stats['uptime']))],
     143                    ['运行时间',            human_time_diff(0,$stats['uptime'])],
     144                    ['已用/分配的内存',    size_format($stats['bytes']).' / '.size_format($stats['limit_maxbytes'])],
     145                    ['当前/启动后总数量',   $stats['curr_items'].' / '.$stats['total_items']],
     146                    ['为获取内存踢除数量',   $stats['evictions']],
     147                    ['当前/总打开连接数',   $stats['curr_connections'].' / '.$stats['total_connections']],
     148                    ['总命中次数',       $stats['get_hits']],
     149                    ['总未命中次数',      $stats['get_misses']],
     150                    ['总获求次数',       $stats['cmd_get']],
     151                    ['总请求次数',       $stats['cmd_set']],
     152                    ['Item平均大小',        size_format($stats['bytes']/$stats['curr_items'])],
     153                ];
     154            }elseif($id == 'efficiency'){
     155                return wpjam_map(['get_hits'=>'命中', 'get_misses'=>'未命中', 'cmd_get'=>'获取', 'cmd_set'=>'设置'], fn($v, $k)=> ['每秒'.$v.'次数', round($stats[$k]/$stats['uptime'])]);
     156            }elseif($id == 'options'){
     157                return wpjam_array(wpjam_get_reflection(['Memcached'], 'Constants'), fn($k, $v)=> str_starts_with($k, 'OPT_') ? [$k, [$k, $GLOBALS['wp_object_cache']->get_mc()->getOption($v)]] : null);
    32158            }
    33        
    34             if(@is_readable('/proc/meminfo')){
    35                 $uptime     = wpjam_lines(file_get_contents('/proc/uptime'), ' ');
    36                 $items[]    = ['title'=>'运行时间', 'value'=>human_time_diff(time()-$uptime[0])];
    37             }
    38 
    39            
    40             $items[]    = ['title'=>'空闲率',      'value'=>round($uptime[1]*100/($uptime[0]*count($cpus)), 2).'%'];
    41             $items[]    = ['title'=>'系统负载', 'value'=>'<strong>'.implode('&nbsp;&nbsp;',sys_getloadavg()).'</strong>'];
    42         }
    43 
    44         $items[]    = ['title'=>'文档根目录',    'value'=>$_SERVER['DOCUMENT_ROOT']];
    45        
    46         self::output($items);
    47     }
    48 
    49     public static function php_widget(){
    50         self::output([['value'=>implode(', ', get_loaded_extensions())]]);
    51     }
    52 
    53     public static function apache_widget(){
    54         self::output([['value'=>implode(', ', apache_get_modules())]]);
    55     }
    56 
    57     public static function version_widget(){
    58         global $wpdb, $required_mysql_version, $required_php_version, $wp_version, $wp_db_version, $tinymce_version;
    59 
    60         $http   = $_SERVER['SERVER_SOFTWARE'];
    61 
    62         self::output([
    63             ['title'=>wpjam_lines($http, '/')[0],   'value'=>$http],
    64             ['title'=>'MySQL',      'value'=>$wpdb->db_version().'(最低要求:'.$required_mysql_version.')'],
    65             ['title'=>'PHP',        'value'=>phpversion().'(最低要求:'.$required_php_version.')'],
    66             ['title'=>'Zend',       'value'=>Zend_Version()],
    67             ['title'=>'WordPress',  'value'=>$wp_version.'('.$wp_db_version.')'],
    68             ['title'=>'TinyMCE',    'value'=>$tinymce_version]
    69         ]);
    70     }
    71 
    72     public static function opcache_status_widget(){
    73         self::output(wpjam_map(opcache_get_status()['opcache_statistics'], fn($v, $k) => ['title'=>$k, 'value'=>$v]));
    74     }
    75 
    76     public static function opcache_usage_widget(){
    77         echo '<p>'.wpjam_get_page_button('reset_opcache').'</p>';
    78 
    79         echo '<hr />';
    80 
    81         $status = opcache_get_status();
    82         $args   = ['chart_width'=>150, 'table_width'=>320];
    83 
    84         $labels = ['used_memory'=>'已用内存', 'free_memory'=>'剩余内存', 'wasted_memory'=>'浪费内存'];
    85         $counts = wpjam_map($labels, fn($v, $k) => ['label'=>$v,    'count'=>round($status['memory_usage'][$k]/(1024*1024),2)]);
    86         $total  = round(array_reduce(array_keys($labels), fn($total, $k) => $total+$status['memory_usage'][$k], 0)/(1024*1024),2);
    87 
    88         wpjam_donut_chart($counts, ['title'=>'内存使用', 'total'=>$total]+$args);
    89 
    90         echo '<hr />';
    91 
    92         $labels = ['hits'=>'命中', 'misses'=>'未命中'];
    93         $counts = wpjam_map($labels, fn($v, $k) => ['label'=>$v,    'count'=>$status['opcache_statistics'][$k]]);
    94         $total  = array_reduce(array_keys($labels), fn($total, $k) => $total+$status['opcache_statistics'][$k], 0);
    95 
    96         wpjam_donut_chart($counts, ['title'=>'命中率', 'total'=>$total]+$args);
    97 
    98         echo '<hr />';
    99 
    100         $counts = [
    101             ['label'=>'已用Keys', 'count'=>$status['opcache_statistics']['num_cached_keys']],
    102             ['label'=>'剩余Keys', 'count'=>$status['opcache_statistics']['max_cached_keys']-$status['opcache_statistics']['num_cached_keys']]
    103         ];
    104 
    105         $total  = $status['opcache_statistics']['max_cached_keys'];
    106 
    107         wpjam_donut_chart($counts, ['title'=>'存储Keys','total'=>$total]+$args);
    108 
    109         // echo '<hr />';
    110 
    111         // $labels  = ['used_memory'=>'已用内存', 'free_memory'=>'剩余内存'];
    112         // $counts  = wpjam_map($labels, fn($v, $k) => ['label'=>$v,    'count'=>round($status['interned_strings_usage'][$k]/(1024*1024),2)]);
    113         // $total   = round(array_reduce(array_keys($labels), fn($total, $k) => $total+$status['interned_strings_usage'][$k], 0)/(1024*1024),2);
    114 
    115         // wpjam_donut_chart($counts, ['title'=>'临时字符串存储内存','total'=>$total]+$args);
    116     }
    117 
    118     public static function opcache_configuration_widget(){
    119         $config = opcache_get_configuration();
    120         $items  = wpjam_map($config['version'], fn($v, $k) => ['title'=>$k, 'value'=>$v]);
    121         $items  = array_merge($items,  wpjam_map($config['directives'], fn($v, $k) => ['title'=>str_replace('opcache.', '', $k), 'value'=>$v]));
    122    
    123         self::output($items);
    124     }
    125 
    126     public static function memcached_usage_widget(){
    127         global $wp_object_cache;
    128 
    129         echo '<p>'.wpjam_get_page_button('flush_mc').'</p>';
    130 
    131         foreach($wp_object_cache->get_stats() as $key => $details){
    132             echo '<hr />';
    133 
    134             $args   = ['chart_width'=>150,'table_width'=>320];
    135             $labels = ['get_hits'=>'命中次数', 'get_misses'=>'未命中次数'];
    136             $counts = wpjam_map($labels, fn($v, $k) => ['label'=>$v,    'count'=>$details[$k]]);
    137 
    138             wpjam_donut_chart($counts, ['title'=>'命中率','total'=>$details['cmd_get']]+$args);
    139 
    140             echo '<hr />';
    141 
    142             $counts = [
    143                 ['label'=>'已用内存',   'count'=>round($details['bytes']/(1024*1024),2)],
    144                 ['label'=>'剩余内存',   'count'=>round(($details['limit_maxbytes']-$details['bytes'])/(1024*1024),2)]
    145             ];
    146 
    147             $total  = round($details['limit_maxbytes']/(1024*1024),2);
    148 
    149             wpjam_donut_chart($counts, ['title'=>'内存使用','total'=>$total]+$args);
    150         }
    151     }
    152 
    153     public static function memcached_status_widget(){
    154         global $wp_object_cache;
    155 
    156         foreach($wp_object_cache->get_stats() as $key => $details){
    157             self::output([
    158                 // ['title'=>'Memcached进程ID',   'value'=>$details['pid']],
    159                 ['title'=>'Memcached地址',    'value'=>$key],
    160                 ['title'=>'Memcached版本',    'value'=>$details['version']],
    161                 ['title'=>'启动时间',           'value'=>wpjam_date('Y-m-d H:i:s',($details['time']-$details['uptime']))],
    162                 ['title'=>'运行时间',           'value'=>human_time_diff(0,$details['uptime'])],
    163                 ['title'=>'已用/分配的内存',   'value'=>size_format($details['bytes']).' / '.size_format($details['limit_maxbytes'])],
    164                 ['title'=>'启动后总数量',     'value'=>$details['curr_items'].' / '.$details['total_items']],
    165                 ['title'=>'为获取内存踢除数量',  'value'=>$details['evictions']],
    166                 ['title'=>'当前/总打开连接数',  'value'=>$details['curr_connections'].' / '.$details['total_connections']],
    167                 ['title'=>'命中次数',           'value'=>$details['get_hits']],
    168                 ['title'=>'未命中次数',      'value'=>$details['get_misses']],
    169                 ['title'=>'总获取请求次数',    'value'=>$details['cmd_get']],
    170                 ['title'=>'总设置请求次数',    'value'=>$details['cmd_set']],
    171                 ['title'=>'Item平均大小',       'value'=>size_format($details['bytes']/$details['curr_items'])],
    172             ]);
    173         }
    174     }
    175 
    176     public static function memcached_options_widget(){
    177         global $wp_object_cache;
    178 
    179         $reflector  = new ReflectionClass('Memcached');
    180         $constants  = wpjam_filter($reflector->getConstants(), fn($v, $k) => str_starts_with($k, 'OPT_'));
    181         $mc         = $wp_object_cache->get_mc();
    182 
    183         self::output(wpjam_map($constants, fn($v, $k) => ['title'=>$k, 'value'=>$mc->getOption($v)]));
    184     }
    185 
    186     public static function memcached_efficiency_widget(){
    187         global $wp_object_cache;
    188 
    189         foreach($wp_object_cache->get_stats() as $key => $details){
    190             self::output([
    191                 ['title'=>'每秒命中次数',     'value'=>round($details['get_hits']/$details['uptime'],2)],
    192                 ['title'=>'每秒未命中次数',    'value'=>round($details['get_misses']/$details['uptime'],2)],
    193                 ['title'=>'每秒获取请求次数',   'value'=>round($details['cmd_get']/$details['uptime'],2)],
    194                 ['title'=>'每秒设置请求次数',   'value'=>round($details['cmd_set']/$details['uptime'],2)],
    195             ]);
    196         }
    197     }
    198 
    199     public static function output($items){
    200         ?>
    201         <table class="widefat striped" style="border:none;">
    202             <tbody><?php foreach($items as $item){ ?>
    203                 <tr><?php if(!empty($item['title'])){ ?>
    204                     <td><?php echo $item['title'] ?></td>
    205                     <td><?php echo $item['value'] ?></td>
    206                 <?php }else{ ?>
    207                     <td colspan="2"><?php echo $item['value'] ?></td>
    208                 <?php } ?></tr>
    209             <?php } ?></tbody>
    210         </table>
    211         <?php
     159        }
    212160    }
    213161
    214162    public static function get_tabs(){
    215         $tabs['server'] = ['title'=>'服务器', 'function'=>'dashboard', 'widgets'=>[
    216             'server'    => ['title'=>'信息',          'callback'=>[self::class, 'server_widget']],
    217             'php'       => ['title'=>'PHP扩展',       'callback'=>[self::class, 'php_widget']],
    218             'version'   => ['title'=>'版本',          'callback'=>[self::class, 'version_widget'],    'context'=>'side'],
    219             'apache'    => ['title'=>'Apache模块',    'callback'=>[self::class, 'apache_widget'],     'context'=>'side']
    220         ]];
     163        $parse  = fn($items)=> array_map(fn($v)=> $v+['callback'=>[self::class, 'callback']], $items);
     164        $tabs   = ['server'=>['title'=>'服务器', 'function'=>'dashboard', 'widgets'=>$parse([
     165            'server'    => ['title'=>'信息'],
     166            'php'       => ['title'=>'PHP扩展'],
     167            'version'   => ['title'=>'版本',          'context'=>'side'],
     168            'apache'    => ['title'=>'Apache模块',    'context'=>'side']
     169        ])]];
    221170
    222171        if(strtoupper(substr(PHP_OS,0,3)) === 'WIN'){
     
    229178
    230179        if(function_exists('opcache_get_status')){
    231             $tabs['opcache']    = ['title'=>'Opcache',  'function'=>'dashboard',    'widgets'=>[
    232                 'usage'         => ['title'=>'使用率', 'callback'=>[self::class, 'opcache_usage_widget']],
    233                 'status'        => ['title'=>'状态',      'callback'=>[self::class, 'opcache_status_widget']],
    234                 'configuration' => ['title'=>'配置信息',    'callback'=>[self::class, 'opcache_configuration_widget'],  'context'=>'side']
    235             ]];
     180            $tabs['opcache']    = ['title'=>'Opcache',  'function'=>'dashboard',    'widgets'=>$parse([
     181                'usage'         => ['title'=>'使用率'],
     182                'status'        => ['title'=>'状态'],
     183                'configuration' => ['title'=>'配置信息',    'context'=>'side']
     184            ])];
    236185
    237186            wpjam_register_page_action('reset_opcache', [
     
    245194
    246195        if(method_exists('WP_Object_Cache', 'get_mc')){
    247             $tabs['memcached']  = ['title'=>'Memcached',    'function'=>'dashboard',    'widgets'=>[
    248                 'usage'         => ['title'=>'使用率', 'callback'=>[self::class, 'memcached_usage_widget']],
    249                 'efficiency'    => ['title'=>'效率',      'callback'=>[self::class, 'memcached_efficiency_widget']],
    250                 'options'       => ['title'=>'选项',      'callback'=>[self::class, 'memcached_options_widget'], 'context'=>'side'],
    251                 'status'        => ['title'=>'状态',      'callback'=>[self::class, 'memcached_status_widget']]
    252             ]];
     196            $tabs['memcached']  = ['title'=>'Memcached',    'function'=>'dashboard',    'widgets'=>$parse([
     197                'usage'         => ['title'=>'使用率'],
     198                'efficiency'    => ['title'=>'效率'],
     199                'options'       => ['title'=>'选项', 'context'=>'side'],
     200                'status'        => ['title'=>'状态']
     201            ])];
    253202
    254203            wpjam_register_page_action('flush_mc', [
  • wpjam-basic/trunk/components/wpjam-admin.php

    r3394812 r3454739  
    126126            $args   = ['post_type'=>get_post_types(['show_ui'=>true, 'public'=>true, '_builtin'=>false])+['post']];
    127127
    128             wpjam_map(['posts', 'drafts'], fn($k)=> add_filter('dashboard_recent_'.$k.'_query_args', fn($query_args)=> array_merge($query_args, $args+['cache_results'=>true])));
     128            wpjam_hooks('dashboard_recent_posts_query_args, dashboard_recent_drafts_query_args', fn($query_args)=> array_merge($query_args, $args+['cache_results'=>true]));
    129129
    130130            add_action('pre_get_comments', fn($query)=> $query->query_vars  = array_merge($query->query_vars, $args+['type'=>'comment']));
  • wpjam-basic/trunk/components/wpjam-basic.php

    r3436010 r3454739  
    218218
    219219            if(self::disabled('privacy', 1)){
    220                 add_action('admin_menu', fn()=> array_map(fn($args)=> remove_submenu_page(...$args), [
     220                add_action('admin_menu', fn()=> wpjam_call_multiple('remove_submenu_page', [
    221221                    ['options-general.php', 'options-privacy.php'],
    222222                    ['tools.php',           'export-personal-data.php'],
  • wpjam-basic/trunk/components/wpjam-cdn.php

    r3356207 r3454739  
    99    public static function get_sections(){
    1010        $cdn_fields = [
    11             'cdn_name'  => ['title'=>'云存储', 'type'=>'select', 'options'=>[''=>'请选择']+wpjam('cdn')+['disabled'=>['title'=>'切回本站', 'description'=>'当使用 CDN 之后想切换回使用本站图片才勾选该选项,并将原 CDN 域名填到「本地设置」的「额外域名」中。']]],
     11            'cdn_name'  => ['title'=>'云存储', 'type'=>'select', 'options'=>[''=>'请选择']+self::get_items()+['disabled'=>['title'=>'切回本站', 'description'=>'当使用 CDN 之后想切换回使用本站图片才勾选该选项,并将原 CDN 域名填到「本地设置」的「额外域名」中。']]],
    1212            'host'      => ['title'=>'CDN 域名',  'type'=>'url',  'description'=>'设置为在CDN云存储绑定的域名。']+self::show_if(),
    1313            'image'     => ['title'=>'图片处理',    'class'=>'switch',  'value'=>1, 'label'=>'开启云存储图片处理功能,使用云存储进行裁图、添加水印等操作。<br /><strong>开启之后,文章和媒体库中的所有图片都会镜像到云存储。</strong>']+self::show_if('image'),
     
    6565            ]]+self::show_if('wm'),
    6666
    67             'volc_imagex_template'  => ['title'=>'火山引擎图片处理模板']+self::show_if('volc_imagex')
     67            'volc_imagex_template'  => ['title'=>'火山引擎图片处理模板', 'show_if'=>['cdn_name', 'volc_imagex']]
    6868        ];
    6969
     
    7575
    7676    public static function show_if($feature=null){
    77         return ['show_if'=>['cdn_name', isset(wpjam('cdn')[$feature]) ? [$feature] : array_keys(wpjam_filter(wpjam('cdn'), fn($item)=> $feature ? in_array($feature, $item['supports'] ?? []) : true))]];
     77        return ['show_if'=>['cdn_name', array_keys(array_filter(self::get_items(), fn($v)=> $feature ? in_array($feature, $v['supports'] ?? []) : true))]];
    7878    }
    7979
     
    8686
    8787    public static function is_exception($url){
    88         static $exceptions;
    89         $exceptions ??= wpjam_lines(self::get_setting('exceptions'));
    90 
    91         return array_any($exceptions, fn($v)=> str_contains($url, $v));
     88        $object = self::get_object();
     89
     90        $object->exceptions ??= wpjam_lines(self::get_setting('exceptions'));
     91
     92        return array_any($object->exceptions, fn($v)=> str_contains($url, $v));
    9293    }
    9394
     
    201202
    202203        if(self::get_setting('image')){
    203             $file   = wpjam('cdn', CDN_NAME.'.file');
    204 
    205             if($file && file_exists($file)){
    206                 $cb = include $file;
    207                 $cb !== 1 && is_callable($cb) && add_filter('wpjam_thumbnail', $cb, 10, 2);
    208             }
    209 
    210             if(self::get_setting('no_subsizes', 1)){
    211                 wpjam_hooks([
    212                     ['wp_calculate_image_srcset_meta',      fn()=> []],
    213                     ['embed_thumbnail_image_size',          fn()=> '160x120'],
    214                     ['intermediate_image_sizes_advanced',   fn($sizes)=> wpjam_pick($sizes, ['full'])],
    215                     ['wp_get_attachment_metadata',          [self::class, 'filter_metadata'], 10, 2],
    216 
    217                     ['wp_img_tag_add_srcset_and_sizes_attr, wp_img_tag_add_width_and_height_attr',  fn()=> false]
    218                 ]);
    219             }
    220 
    221             if(self::get_setting('thumbnail', 1)){
    222                 wpjam_hooks([
    223                     ['render_block_core/image', [self::class, 'filter_image_block'], 5, 2],
    224                     ['wp_content_img_tag',      [self::class, 'filter_img_tag'], 1, 3]
    225                 ]);
    226             }
     204            $file   = self::get_item(CDN_NAME.'[file]') ?? dirname(__DIR__).'/cdn'.'/'.CDN_NAME.'.php';
     205
     206            $file && file_exists($file) && ($cb = include $file) && is_callable($cb) && add_filter('wpjam_thumbnail', $cb, 10, 2);
     207
     208            self::get_setting('no_subsizes', 1) && wpjam_hooks([
     209                ['wp_calculate_image_srcset_meta',      fn()=> []],
     210                ['embed_thumbnail_image_size',          fn()=> '160x120'],
     211                ['intermediate_image_sizes_advanced',   fn($sizes)=> wpjam_pick($sizes, ['full'])],
     212                ['wp_get_attachment_metadata',          [self::class, 'filter_metadata'], 10, 2],
     213
     214                ['wp_img_tag_add_srcset_and_sizes_attr, wp_img_tag_add_width_and_height_attr',  fn()=> false]
     215            ]);
     216
     217            self::get_setting('thumbnail', 1) && wpjam_hooks([
     218                ['render_block_core/image', [self::class, 'filter_image_block'], 5, 2],
     219                ['wp_content_img_tag',      [self::class, 'filter_img_tag'], 1, 3]
     220            ]);
    227221
    228222            wpjam_hooks([
     
    242236        }
    243237
    244         if(!wpjam_basic_get_setting('upload_external_images') && self::get_setting('remote') && !is_multisite()){
    245             include dirname(__DIR__).'/cdn/remote.php';
    246         }
     238        self::get_setting('remote') && !wpjam_basic_get_setting('upload_external_images') && !is_multisite() && (include dirname(__DIR__).'/cdn/remote.php');
    247239    }
    248240
    249241    public static function add_hooks(){
    250         wpjam_map([
     242        self::add_items([
    251243            'aliyun_oss'    => [
    252244                'title'         => '阿里云OSS',
     
    269261            ],
    270262            'ucloud'        => ['title'=>'UCloud']
    271         ], fn($v, $k)=> wpjam('cdn', $k, $v+['file'=>dirname(__DIR__).'/cdn'.'/'.$k.'.php']));
    272 
    273         if(self::get_setting('disabled')){
    274             self::update_setting('cdn_name', 'disabled');
    275             self::delete_setting('disabled');
    276         }
    277 
    278         if(self::get_setting('image') && !self::get_setting('img_exts')){
    279             self::update_setting('img_exts', 1);
    280         }
     263        ]);
    281264
    282265        add_action('plugins_loaded', [self::class, 'on_plugins_loaded'], 99);
     
    285268
    286269function wpjam_register_cdn($name, $args){
    287     return wpjam('cdn', $name, $args);
     270    return WPJAM_CDN::add_item($name, $args);
    288271}
    289272
    290273function wpjam_unregister_cdn($name){
    291     return wpjam('cdn[]', $name, null);
     274    return WPJAM_CDN::delete_item($name);
    292275}
    293276
  • wpjam-basic/trunk/components/wpjam-custom.php

    r3356207 r3454739  
    4747            if($value = get_post_meta(get_the_ID(), 'custom_'.$name, true)){
    4848                if($name == 'head'){
    49                     add_action('wp_'.$name, fn()=> wpjam_echo($value), 99);
     49                    wpjam_hook('echo', 'wp_'.$name, fn()=> $value, 99);
    5050                }else{
    5151                    echo $value;
  • wpjam-basic/trunk/components/wpjam-enhance.php

    r3423058 r3454739  
    44Version: 2.0
    55*/
    6 class WPJAM_Enhance{
     6class WPJAM_Enhance extends WPJAM_Option_Model{
    77    public static function get_section(){
    88        $options    = array_column(get_taxonomies(['public'=>true, 'hierarchical'=>true], 'objects'), 'label', 'name');
     
    2323        add_filter('wp_update_attachment_metadata', fn($data)=> (isset($data['thumb']) ? ['thumb'=>basename($data['thumb'])] : [])+$data);
    2424
    25         $options    = wpjam_basic_get_setting('x-frame-options');
     25        $options    = self::get_setting('x-frame-options');
    2626        $options    && add_action('send_headers', fn()=> header('X-Frame-Options: '.$options));
    2727
    2828        // 防止重名造成大量的 SQL
    29         if(wpjam_basic_get_setting('timestamp_file_name')){
     29        if(self::get_setting('timestamp_file_name')){
    3030            wpjam_hooks('wp_handle_sideload_prefilter, wp_handle_upload_prefilter', fn($file)=> array_merge($file, empty($file['md5_filename']) ? ['name'=> time().'-'.$file['name']] : []));
    3131        }
    3232
    33         if(wpjam_basic_get_setting('no_category_base')){
    34             $tax    = wpjam_basic_get_setting('no_category_base_for', 'category');
     33        if(self::get_setting('no_category_base')){
     34            $tax    = self::get_setting('no_category_base_for', 'category');
    3535
    3636            $tax == 'category' && str_starts_with($_SERVER['REQUEST_URI'], '/category/') && add_action('template_redirect', fn()=> wp_redirect(site_url(substr($_SERVER['REQUEST_URI'], 10)), 301));
     
    4141}
    4242
    43 class WPJAM_Gravatar{
     43class WPJAM_Gravatar extends WPJAM_Option_Model{
    4444    public static function get_fields(){
    4545        return ['gravatar'=>['title'=>'Gravatar加速', 'label'=>true, 'type'=>'fieldset', 'fields'=>[
    46             'gravatar'=>['after'=>'加速服务', 'options'=>wpjam_options('gravatar')+['custom'=>[
     46            'gravatar'=>['after'=>'加速服务', 'options'=>wpjam_options(self::get_items(), ['type'=>'select'])+['custom'=>[
    4747                'title'     => '自定义',   
    4848                'fields'    => ['gravatar_custom'=>['placeholder'=>'请输入 Gravatar 加速服务地址']]
     
    7777        }
    7878
    79         $name   = wpjam_basic_get_setting('gravatar');
    80         $value  = $name == 'custom' ? wpjam_basic_get_setting('gravatar_custom') : ($name ? wpjam('gravatar', $name.'.url') : '');
     79        $name   = self::get_setting('gravatar');
     80        $value  = $name == 'custom' ? self::get_setting('gravatar_custom') : ($name ? self::get_item($name.'[url]') : '');
    8181
    8282        $value && add_filter('get_avatar_url', fn($url)=> str_replace(array_map(fn($v)=>$v.'gravatar.com/avatar/', ['https://secure.', 'http://0.', 'http://1.', 'http://2.']), $value, $url));
     
    8686
    8787    public static function add_hooks(){
    88         wpjam_map([
     88        self::add_items([
    8989            'geekzu'    => ['title'=>'极客族',     'url'=>'https://sdn.geekzu.org/avatar/'],
    9090            'loli'      => ['title'=>'loli',        'url'=>'https://gravatar.loli.net/avatar/'],
     
    9292            '7ed'       => ['title'=>'7ED',         'url'=>'https://use.sevencdn.com/avatar/'],
    9393            'cravatar'  => ['title'=>'Cravatar',    'url'=>'https://cravatar.cn/avatar/'],
    94         ], fn($v, $k)=> wpjam('gravatar', $k, $v));
     94        ]);
    9595
    9696        add_filter('pre_get_avatar_data', [self::class, 'filter_pre_data'], 10, 2);
     
    9898}
    9999
    100 class WPJAM_Google_Font{
     100class WPJAM_Google_Font extends WPJAM_Option_Model{
    101101    public static function get_search(){
    102102        return [
     
    110110    public static function get_fields(){
    111111        return ['google_fonts'=>['title'=>'Google字体加速', 'type'=>'fieldset', 'label'=>true, 'fields'=>[
    112             'google_fonts'=>['type'=>'select', 'after'=>'加速服务', 'options'=>wpjam_options('google_font')+['custom'=>[
     112            'google_fonts'=>['type'=>'select', 'after'=>'加速服务', 'options'=>wpjam_options(self::get_items(), ['type'=>'select'])+['custom'=>[
    113113                'title'     => '自定义',
    114114                'fields'    => wpjam_map(self::get_search(), fn($v)=> ['placeholder'=>'请输入'.str_replace('//', '', $v).'加速服务地址'])
     
    118118
    119119    public static function add_hooks(){
    120         wpjam_map([
     120        self::add_items([
    121121            'geekzu'    => [
    122122                'title'     => '极客族',
     
    131131                'replace'   => ['//fonts.lug.ustc.edu.cn', '//ajax.lug.ustc.edu.cn', '//google-themes.lug.ustc.edu.cn', '//fonts-gstatic.lug.ustc.edu.cn']
    132132            ]
    133         ], fn($v, $k)=> wpjam('google_font', $k, $v));
     133        ]);
    134134
    135135        $search = self::get_search();
    136         $name   = wpjam_basic_get_setting('google_fonts');
    137         $value  = $name == 'custom' ? wpjam_map($search, fn($v, $k)=> str_replace(['http://','https://'], '//', wpjam_basic_get_setting($k) ?: $v)) : ($name ? wpjam('google_font', $name.'.replace') : '');
     136        $name   = self::get_setting('google_fonts');
     137        $value  = $name == 'custom' ? wpjam_map($search, fn($v, $k)=> str_replace(['http://','https://'], '//', self::get_setting($k) ?: $v)) : ($name ? self::get_item($name.'[replace]') : '');
    138138
    139139        $value && add_filter('wpjam_html', fn($html)=> str_replace($search, $value, $html));
     
    141141}
    142142
    143 class WPJAM_Static_CDN{
    144     public static function get_setting(){
    145         $hosts  = wpjam('static_cdn');
    146         $host   = wpjam_basic_get_setting('static_cdn');
     143class WPJAM_Static_CDN extends WPJAM_Option_Model{
     144    public static function get_value(){
     145        $hosts  = self::get_items();
     146        $host   = self::get_setting('static_cdn');
    147147
    148148        return $host && in_array($host, $hosts) ? $host : $hosts[0];
     
    150150
    151151    public static function get_fields(){
    152         return ['static_cdn'=>['title'=>'前端公共库', 'options'=>wpjam_fill(wpjam('static_cdn'), fn($v)=> parse_url($v, PHP_URL_HOST))]];
     152        return ['static_cdn'=>['title'=>'前端公共库', 'options'=>wpjam_fill(self::get_items(), fn($v)=> parse_url($v, PHP_URL_HOST))]];
    153153    }
    154154
    155155    public static function add_hooks(){
    156         wpjam_map([
     156        self::add_items([
    157157            'https://cdnjs.cloudflare.com/ajax/libs',
    158158            'https://s4.zstatic.net/ajax/libs',
     
    161161            'https://cdnjs.loli.net/ajax/libs',
    162162            'https://use.sevencdn.com/ajax/libs',
    163         ], fn($v)=> wpjam('static_cdn[]', $v));
     163        ]);
    164164
    165         $host   = self::get_setting();
    166         $hosts  = array_diff(wpjam('static_cdn'), [$host]);
     165        $host   = self::get_value();
     166        $hosts  = array_diff(self::get_items(), [$host]);
    167167
    168168        foreach(['style', 'script'] as $type){
     
    182182
    183183function wpjam_register_gravatar($name, $args){
    184     return wpjam('gravatar', $name, $args);
     184    return WPJAM_Gravatar::add_item($name, $args);
    185185}
    186186
    187187function wpjam_register_google_font($name, $args){
    188     return wpjam('google_font', $name, $args);
     188    return WPJAM_Google_Font::add_item($name, $args);
    189189}
    190190
    191191function wpjam_add_static_cdn($host){
    192     return wpjam('static_cdn[]', $host);
     192    return WPJAM_Static_CDN::add_item($host);
    193193}
    194194
    195195function wpjam_get_static_cdn(){
    196     return WPJAM_Static_CDN::get_setting();
     196    return WPJAM_Static_CDN::get_value();
    197197}
  • wpjam-basic/trunk/components/wpjam-thumbnail.php

    r3423058 r3454739  
    4747    public static function get_default(){
    4848        $default    = self::get_setting('default', []);
    49         $default    = ($default && is_array($default)) ? $default[array_rand($default)] : '';
     49        $default    = $default && is_array($default) ? $default[array_rand($default)] : '';
    5050
    5151        return apply_filters('wpjam_default_thumbnail_url', $default);
  • wpjam-basic/trunk/extends/mobile-theme.php

    r3356207 r3454739  
    66Version: 2.0
    77*/
    8 class WPJAM_Mobile_Stylesheet{
     8class WPJAM_Mobile_Stylesheet extends WPJAM_Option_Model{
    99    public static function get_fields(){
    1010        $themes = array_map(fn($v)=> $v->get('Name'), wp_get_themes(['allowed'=>true]));
     
    1414    }
    1515
    16     public static function builtin_page_load(){
    17         $mobile = wpjam_basic_get_setting('mobile_stylesheet');
     16    public static function load(){
     17        $mobile = self::get_setting('mobile_stylesheet');
    1818        $button = wpjam_register_page_action('set_mobile_stylesheet', [
    1919            'button_text'   => '移动主题',
     
    2222            'confirm'       => true,
    2323            'response'      => 'redirect',
    24             'callback'      => fn()=> WPJAM_Basic::update_setting('mobile_stylesheet', wpjam_get_data_parameter('stylesheet'))
     24            'callback'      => fn()=> self::update_setting('mobile_stylesheet', wpjam_get_data_parameter('stylesheet'))
    2525        ])->get_button(['data'=>['stylesheet'=>'slug']]);
    2626
     
    4141
    4242    public static function add_hooks(){
    43         $name   = wp_is_mobile() ? wpjam_basic_get_setting('mobile_stylesheet') : null;
     43        $name   = wp_is_mobile() ? self::get_setting('mobile_stylesheet') : null;
    4444        $name   = $name ?: ($_GET['wpjam_theme'] ?? null);
    4545        $theme  = $name ? wp_get_theme($name) : null;
     
    5050
    5151wpjam_add_option_section('wpjam-basic', 'enhance', ['model'=>'WPJAM_Mobile_Stylesheet', 'admin_load'=>['base'=>'themes']]);
    52 
  • wpjam-basic/trunk/extends/quick-excerpt.php

    r3356207 r3454739  
    2727            }
    2828
    29             add_filter('wp_insert_post_data', fn($data)=> isset($_POST['the_excerpt']) ? wpjam_set($data, 'post_excerpt', $_POST['the_excerpt']) : $data);
     29            isset($_POST['the_excerpt']) && add_filter('wp_insert_post_data', fn($data)=> wpjam_set($data, 'post_excerpt', $_POST['the_excerpt']));
    3030
    31             add_action('add_inline_data', fn($post)=> wpjam_echo('<div class="post_excerpt">'.esc_textarea(trim($post->post_excerpt)).'</div>'));
     31            wpjam_hook('echo', 'add_inline_data', fn($post)=> '<div class="post_excerpt">'.esc_textarea(trim($post->post_excerpt)).'</div>');
    3232        }
    3333    }
  • wpjam-basic/trunk/extends/wpjam-roles.php

    r3423058 r3454739  
    126126            add_filter('additional_capabilities_display', '__return_false' );
    127127
    128             wpjam_map(['show', 'edit'], fn($v)=> add_action($v.'_user_profile', fn($user)=> wpjam_echo('<h3>额外权限</h3>'.self::get_additional($user, 'fields'))));
     128            wpjam_map(['show', 'edit'], fn($v)=> wpjam_hook('echo', $v.'_user_profile', fn($user)=> '<h3>额外权限</h3>'.self::get_additional($user, 'fields')));
    129129
    130130            wpjam_map(['personal_options_update', 'edit_user_profile_update'], fn($v)=> add_action($v, fn($id)=> self::set_additional($id, wpjam_get_post_parameter('capabilities') ?: [])));
  • wpjam-basic/trunk/extends/wpjam-seo.php

    r3356207 r3454739  
    171171    public static function add_hooks(){
    172172        if(self::get_setting('unique')){
    173             add_filter('wpjam_html',    [self::class, 'filter_html']);
    174         }else{
    175             add_action('wp_head',       fn()=> wpjam_echo(implode(self::get_value('meta'))));
     173            add_filter('wpjam_html', [self::class, 'filter_html']);
     174        }else{
     175            wpjam_hook('echo', 'wp_head', fn()=> implode(self::get_value('meta')));
    176176        }
    177177
  • wpjam-basic/trunk/extends/wpjam-toc.php

    r3436010 r3454739  
    77*/
    88class WPJAM_Toc extends WPJAM_Option_Model{
    9     private static $toc = [];
    10 
    119    public static function get_fields(){
    1210        $fields = array_filter([
     
    2826        $path   = [];
    2927
    30         foreach(self::$toc as $item){
     28        foreach(self::get_items() as $item){
    3129            $depth  = $item['depth'];
    3230
     
    4846    }
    4947
    50     public static function add_item($m, $index=false){
     48    public static function callback($m, $index=false, $added=false){
    5149        $attr   = $m[2] ? shortcode_parse_atts($m[2]) : [];
    5250        $attr   = wp_parse_args($attr, ['class'=>'', 'id'=>'']);
     
    5452        if(!$attr['class'] || !str_contains($attr['class'], 'toc-noindex')){
    5553            $attr['class']  = wpjam_join(' ', $attr['class'], ($index ? 'toc-index' : ''));
    56             $attr['id']     = $attr['id'] ?: 'toc_'.(count(self::$toc)+1);
    57             self::$toc[]    = ['text'=>trim(strip_tags($m[3])), 'depth'=>$m[1], 'id'=>$attr['id']];
     54            $attr['id']     = $attr['id'] ?: 'toc_'.(count(self::get_items())+1);
     55           
     56            $added || self::add_item(['text'=>trim(strip_tags($m[3])), 'depth'=>$m[1], 'id'=>$attr['id']]);
    5857        }
    5958
     
    7574
    7675        if($depth){
    77             self::$toc  = [];
     76            $added      = (bool)self::get_items();
    7877            $index      = str_contains($content, '[toc]');
    79             $position   = self::get_setting('position', 'content');
    80             $content    = wpjam_preg_replace('#<h([1-'.$depth.'])\b([^>]*)>(.*?)</h\1>#', fn($m)=> self::add_item($m, $index), $content);
     78            $content    = wpjam_preg_replace('#<h([1-'.$depth.'])\b([^>]*)>(.*?)</h\1>#', fn($m)=> self::callback($m, $index, $added), $content);
    8179
    8280            if($index){
    8381                return str_replace('[toc]', self::render(), $content);
    84             }elseif($position == 'content'){
     82            }elseif(self::get_setting('position', 'content') == 'content'){
    8583                return self::render().$content;
    8684            }
  • wpjam-basic/trunk/includes/class-wpjam-admin.php

    r3440469 r3454739  
    2020
    2121        $msg && $type && $this->update_arg('error[]', compact('msg', 'type'));
    22     }
    23 
    24     public function chart($method, ...$args){
    25         if(is_object($method)){
    26             return $this->chart = $method;
    27         }
    28 
    29         return $this->chart ? $this->chart->$method(...$args) : null;
    3022    }
    3123
     
    4436    }
    4537
    46     public function load($screen=''){
    47         if($screen){
    48             if($screen->base == 'post'){
    49                 $this->post_id  = (int)($_GET['post'] ?? ($_POST['post_ID'] ?? 0));
    50             }
    51 
     38    public function load(...$args){
     39        if(count($args) >= 2){
     40            $map    = ['builtin_page'=>'base', 'plugin_page'=>'plugin_page'];
     41            $type   = $args[0] ?? array_find_key($map, fn($v)=> isset($args[1][$v]));
     42
     43            return isset($map[$type]) && $this->update_arg($type.'_load[]', $args[1]);
     44        }
     45
     46        if($screen = array_shift($args)){
    5247            $this->screen   = $screen;
    5348            $this->vars     = ['screen_id'=>$screen->id]+array_filter(wpjam_pick($screen, ['post_type', 'taxonomy']));
     49
     50            $screen->base == 'post' && ($this->post_id = (int)($_GET['post'] ?? ($_POST['post_ID'] ?? 0)));
    5451        }
    5552
     
    466463        }
    467464
    468         $this->chart    && wpjam_admin('chart', WPJAM_Chart::get_instance($this->chart));
     465        $this->chart    && wpjam_chart(is_array($this->chart) ? $this->chart : []);
    469466        $this->editor   && add_action('admin_footer', 'wp_enqueue_editor');
    470467
     
    505502        }else{
    506503            $function       = $this->function ?: wpjam_get_filter_name($this->name, 'page');
    507             $this->render   = is_callable($function) ? fn()=> wpjam_admin('chart', 'render').wpjam_ob($function) : $this->throw('页面函数'.'「'.$function.'」未定义。');
     504            $this->render   = is_callable($function) ? fn()=> wpjam_chart('render').wpjam_ob($function) : $this->throw('页面函数'.'「'.$function.'」未定义。');
    508505        }
    509506    }
     
    754751
    755752class WPJAM_Chart extends WPJAM_Args{
    756     public function get_parameter($key, $args=[]){
    757         if(str_contains($key, 'timestamp')){
    758             return wpjam_strtotime($this->get_parameter(str_replace('timestamp', 'date', $key), $args).' '.(str_starts_with($key, 'end_') ? '23:59:59' : '00:00:00'));
    759         }
    760 
    761         $data   = $args['data'] ?? null;
    762         $method = $args['method'] ?? $this->method;
    763         $value  = (is_array($data) && !empty($data[$key])) ? $data[$key] : wpjam_get_parameter($key, ['method'=>$method]);
    764 
    765         if($value){
    766             wpjam_set_cookie($key, $value, HOUR_IN_SECONDS);
    767 
    768             return $value;
    769         }
    770 
    771         if(!empty($_COOKIE[$key])){
    772             return $_COOKIE[$key];
    773         }
    774 
    775         if($key == 'date_format' || $key == 'date_type'){
    776             return '%Y-%m-%d';
    777         }elseif($key == 'compare'){
    778             return 0;
    779         }elseif(str_contains($key, 'date')){
    780             if($key == 'start_date'){
    781                 $ts = time() - DAY_IN_SECONDS*30;
    782             }elseif($key == 'end_date'){
    783                 $ts = time();
    784             }elseif($key == 'date'){
    785                 $ts = time() - DAY_IN_SECONDS;
    786             }elseif($key == 'start_date_2'){
    787                 $ts = $this->get_parameter('end_timestamp_2') - ($this->get_parameter('end_timestamp') - $this->get_parameter('start_timestamp'));
    788             }elseif($key == 'end_date_2'){
    789                 $ts = $this->get_parameter('start_timestamp') - DAY_IN_SECONDS;
    790             }
    791 
    792             return wpjam_date('Y-m-d', $ts);
    793         }
    794     }
    795 
    796     public function get_fields($args=[]){
    797         if($this->show_start_date){
    798             $fields['date'] = ['sep'=>' ',  'fields'=>[
    799                 'start_date'    => ['type'=>'date', 'value'=>$this->get_parameter('start_date', $args)],
    800                 'date_view'     => ['type'=>'view', 'value'=>'-'],
    801                 'end_date'      => ['type'=>'date', 'value'=>$this->get_parameter('end_date', $args)]
    802             ]];
    803         }elseif($this->show_date){
    804             $fields['date'] = ['sep'=>' ',  'fields'=>[
    805                 'prev_day'  => ['type'=>'button',   'value'=>'‹',   'class'=>'button prev-day'],
    806                 'date'      => ['type'=>'date',     'value'=>$this->get_parameter('date', $args)],
    807                 'next_day'  => ['type'=>'button',   'value'=>'›',   'class'=>'button next-day']
    808             ]];
    809         }
    810 
    811         if(isset($fields['date']) && !empty($args['show_title'])){
    812             $fields['date']['title']    = '日期';
    813         }
    814 
    815         if($this->show_date_type){
    816             $fields['date_format']  = ['type'=>'select','value'=>$this->get_parameter('date_format', $args), 'options'=>[
    817                 '%Y-%m'             => '按月',
    818                 '%Y-%m-%d'          => '按天',
    819                 // '%Y%U'           => '按周',
    820                 '%Y-%m-%d %H:00'    => '按小时',
    821                 '%Y-%m-%d %H:%i'    => '按分钟',
    822             ]];
    823         }
    824 
    825         return $fields;
    826     }
    827 
    828     public function get_data($args=[]){
    829         $keys   = $this->show_start_date ? ['start_date', 'end_date'] : ($this->show_date ? ['date'] : []);
    830 
    831         return wpjam_fill($keys, fn($k)=> $this->get_parameter($k, $args));
     753    public function validate($data){
     754        $fields = wpjam_fields($this->get_fields(['data'=>$data]));
     755
     756        return $fields->validate($data+$fields->get_defaults());
    832757    }
    833758
     
    869794    }
    870795
    871     public static function line($args=[], $type='Line'){
    872         $args   += [
    873             'data'          => [],
    874             'labels'        => [],
    875             'day_labels'    => [],
    876             'day_label'     => '时间',
    877             'day_key'       => 'day',
    878             'chart_id'      => 'daily-chart',
    879             'show_table'    => true,
    880             'show_chart'    => true,
    881             'show_sum'      => true,
    882             'show_avg'      => true,
    883         ];
    884 
    885         foreach($args['labels'] as $k => $v){
     796    public function get_parameter($key, $args=[]){
     797        if(is_array($key)){
     798            return wpjam_fill($key, fn($k)=> $this->get_parameter($k, $args)); 
     799        }
     800
     801        if(str_contains($key, 'timestamp')){
     802            return wpjam_strtotime($this->get_parameter(str_replace('timestamp', 'date', $key), $args).' '.(str_starts_with($key, 'end_') ? '23:59:59' : '00:00:00'));
     803        }
     804
     805        $value  = ($args['data'][$key] ?? '') ?: wpjam_get_parameter($key, ['method'=>($args['method'] ?? $this->method)]);
     806        $value && wpjam_set_cookie($key, $value, HOUR_IN_SECONDS);
     807
     808        if(!empty($_COOKIE[$key])){
     809            return $_COOKIE[$key];
     810        }
     811
     812        if($key == 'date_format' || $key == 'date_type'){
     813            return '%Y-%m-%d';
     814        }elseif($key == 'compare'){
     815            return 0;
     816        }
     817
     818        if($key == 'date'){
     819            $ts = time() - DAY_IN_SECONDS;
     820        }elseif($key == 'start_date'){
     821            $ts = time() - DAY_IN_SECONDS*30;
     822        }elseif($key == 'end_date'){
     823            $ts = time();
     824        }elseif($key == 'start_date_2'){
     825            $ts = $this->get_parameter('end_timestamp_2') - ($this->get_parameter('end_timestamp') - $this->get_parameter('start_timestamp'));
     826        }elseif($key == 'end_date_2'){
     827            $ts = $this->get_parameter('start_timestamp') - DAY_IN_SECONDS;
     828        }
     829
     830        return isset($ts) ? wpjam_date('Y-m-d', $ts) : null;
     831    }
     832
     833    public function get_fields($args=[]){
     834        $fields = $this->show_start_date ? [
     835            'start_date'=> ['type'=>'date'],
     836            'date_view' => ['type'=>'view',     'value'=>'-'],
     837            'end_date'  => ['type'=>'date']
     838        ] : ($this->show_date ? [
     839            'prev_day'  => ['type'=>'button',   'value'=>'‹',   'class'=>'button prev-day'],
     840            'date'      => ['type'=>'date'],
     841            'next_day'  => ['type'=>'button',   'value'=>'›',   'class'=>'button next-day']
     842        ] : []);
     843
     844        $fields = $fields ? ['date'=>['sep'=>' ', 'fields'=>wpjam_map($fields, fn($v, $k)=> ['value'=>($v['value'] ?? $this->get_parameter($k, $args))]+$v)]+(empty($args['show_title']) ? ['title'=>'日期'] : [])] : [];
     845
     846        return $fields+($this->show_date_type ? ['date_format'=>['type'=>'select','value'=>$this->get_parameter('date_format', $args), 'options'=>[
     847            '%Y-%m'             => '按月',
     848            '%Y-%m-%d'          => '按天',
     849            // '%Y%U'           => '按周',
     850            '%Y-%m-%d %H:00'    => '按小时',
     851            '%Y-%m-%d %H:%i'    => '按分钟',
     852        ]]] : []);
     853    }
     854
     855    public static function line($data, $args=[], $type='Line'){
     856        foreach(($args['labels'] ?? []) as $k => $v){
    886857            if(is_array($v)){
    887                 $args['columns'][$k]    = $v['label'];
    888 
    889                 if(!isset($v['show_in_chart']) || $v['show_in_chart']){
     858                $columns[$k]    = $v['label'];
     859
     860                if($v['show_in_chart'] ?? true){
    890861                    $labels[$k] = $v['label'];
    891862                }
     
    895866                }
    896867            }else{
    897                 $args['columns'][$k]    = $labels[$k] = $v;
    898             }
    899         }
    900 
    901         $parser = fn($item)=> empty($cbs) ? $item : array_merge($item, array_map(fn($cb)=> $cb($item), $cbs));
    902         $data   = $total = [];
    903 
    904         if($args['show_table']){
    905             $args['day_labels'] += ['sum'=>'累加', 'avg'=>'平均'];
    906 
    907             $row    = self::row('head', [], $args);
    908             $thead  = wpjam_tag('thead')->append($row);
    909             $tfoot  = wpjam_tag('tfoot')->append($row);
    910             $tbody  = wpjam_tag('tbody');
    911         }
    912 
    913         foreach($args['data'] as $day => $item){
    914             $item   = $parser((array)$item);
    915             $day    = $item[$args['day_key']] ?? $day;
    916             $total  = wpjam_map($args['columns'], fn($v, $k)=> ($total[$k] ?? 0)+((isset($item[$k]) && is_numeric($item[$k])) ? $item[$k] : 0));
    917             $data[] = array_merge([$args['day_key']=> $day], array_intersect_key($item, $labels));
    918 
    919             $args['show_table'] && $tbody->append(self::row($day, $item, $args));
    920         }
    921 
     868                $columns[$k]    = $labels[$k] = $v;
     869            }
     870        }
     871
     872        $xkey   = $args['day_key'] ?? 'day';
     873        $ykeys  = array_keys($labels);
     874        $data   += ($args['show_table'] ?? true) && $data ? ['total' => array_fill_keys($ykeys, 0)] : [];
     875
     876        foreach($data as $day => &$item){
     877            $item   = isset($cbs) ? array_merge((array)$item, array_map(fn($cb)=> $cb($item), $cbs)) : $item;
     878
     879            if($day !== 'total'){
     880                $item[$xkey] ??= $day;
     881
     882                if(isset($data['total'])){
     883                    $data['total']      = wpjam_map($data['total'], fn($v, $k)=> $v+(is_numeric($item[$k] ?? null) ? $item[$k] : 0));
     884                    $rows[$item[$xkey]] = $item;
     885                }
     886
     887                $item   = wpjam_pull($item, [...$ykeys, $xkey]);
     888            }
     889        }
     890
     891        $total  = wpjam_pull($data, 'total');
    922892        $tag    = wpjam_tag();
    923893
    924         $args['show_chart'] && $data && wpjam_tag('div', ['id'=>$args['chart_id']])->data(['chart'=>true, 'type'=>$type, 'options'=>['data'=>$data, 'xkey'=>$args['day_key'], 'ykeys'=>array_keys($labels), 'labels'=>array_values($labels)]])->append_to($tag);
    925 
    926         if($args['show_table'] && $args['data']){
    927             $total  = $parser($total);
    928 
    929             $args['show_sum'] && $tbody->append(self::row('sum', $total, $args));
    930             $args['show_avg'] && $tbody->append(self::row('avg', array_map(fn($v)=> is_numeric($v) ? round($v/count($args['data'])) : '', $total), $args));
    931 
    932             $thead->after([$tbody, $tfoot])->wrap('table', ['class'=>'wp-list-table widefat striped'])->append_to($tag);
     894        if($args['show_chart'] ?? true){
     895            $tag->append(wpjam_tag('div', [
     896                'id'    => $args['chart_id'] ?? 'daily-chart',
     897                'data'  => ['type'=>$type, 'chart'=>true, 'options'=>['data'=>$data, 'xkey'=>$xkey, 'ykeys'=>$ykeys, 'labels'=>array_values($labels)]]
     898            ]));
     899        }
     900
     901        if($total && $data){
     902            $labels     = ($args['day_labels'] ?? [])+['sum'=>'累加', 'avg'=>'平均'];
     903            $columns    = [$xkey => $args['day_label'] ?? '时间']+$columns;
     904            $th         = wpjam_tag('tr')->append(wpjam_map($columns, fn($col, $id)=> wpjam_tag('th', ['scope'=>'col', 'id'=>$id], $col)));
     905            $rows       += ($args['show_sum'] ?? true) ? ['sum'=>$total] : [];
     906            $rows       += ($args['show_avg'] ?? true) ? ['avg'=>array_map(fn($v)=> is_numeric($v) ? round($v/count($data)) : '', $total)] : [];
     907            $rows       = wpjam_map($rows, fn($row, $k)=> wpjam_tag('tr')->append(wpjam_map($columns, fn($col, $id)=> wpjam_tag('td', [
     908                'data'  => ['colname'=>$col],
     909                'class' => ['column-'.$id, ($id == $xkey ? 'column-primary' : '')]
     910            ])->append($id == $xkey ? [($labels[$k] ?? $k), wpjam_tag('button', ['class'=>'toggle-row'])] : ($row[$id] ?? '')))));
     911
     912            $tag->append(wpjam_tag('table', ['class'=>'wp-list-table widefat striped'])->append([
     913                wpjam_tag('thead')->append($th),
     914                wpjam_tag('tbody')->append($rows),
     915                wpjam_tag('tfoot')->append($th)
     916            ]));
    933917        }
    934918
     
    936920    }
    937921
    938     public static function donut($args=[]){
    939         $args   += [
    940             'data'          => [],
    941             'total'         => 0,
    942             'title'         => '名称',
    943             'key'           => 'type',
    944             'chart_id'      => 'chart_'.wp_generate_password(6, false, false),
    945             'show_table'    => true,
    946             'show_chart'    => true,
    947             'show_line_num' => false,
    948             'labels'        => []
    949         ];
    950 
    951         if($args['show_table']){
    952             $thead  = wpjam_tag('thead')->append(self::row('head', '', $args));
    953             $tbody  = wpjam_tag('tbody');
    954         }
    955 
    956         foreach(array_values($args['data']) as $i => $item){
    957             $label  = $item['label'] ?? '/';
    958             $label  = $args['labels'][$label] ?? $label;
    959             $value  = $item['count'];
    960             $data[] = ['label'=>$label, 'value'=>$value];
    961 
    962             $args['show_table'] && $tbody->append(self::row($i+1, $value, ['label'=>$label]+$args));
    963         }
    964 
    965         $tag    = wpjam_tag();
    966 
    967         $args['show_chart'] && $tag->append('div', ['id'=>$args['chart_id'], 'data'=>['chart'=>true, 'type'=>'Donut', 'options'=>['data'=>$data ?? []]]]);
    968 
    969         if($args['show_table']){
    970             $args['total'] && $tbody->append(self::row('total', $args['total'], $args+['label'=>'所有']));
    971 
    972             $tag->append('table', ['wp-list-table', 'widefat', 'striped'], implode('', [$thead, $tbody]));
    973         }
    974 
    975         return $tag->wrap('div', ['class'=>'donut-chart-wrap']);
    976     }
    977 
    978     protected static function row($key, $data=[], $args=[]){
    979         $row    = wpjam_tag('tr');
    980 
    981         if(is_array($data)){
    982             $day_key    = $args['day_key'];
    983             $columns    = [$day_key=>$args['day_label']]+$args['columns'];
    984             $data       = [$day_key=>$args['day_labels'][$key] ?? $key]+$data;
    985 
    986             foreach($columns as $col => $column){
    987                 $cell   = wpjam_tag(...($key == 'head' ? ['th', ['scope'=>'col', 'id'=>$col], $column] : ['td', ['data'=>['colname'=>$column]], $data[$col] ?? '']));
    988 
    989                 $col == $day_key && $cell->add_class('column-primary')->append('button', ['class'=>'toggle-row']);
    990                 $cell->add_class('column-'.$col)->append_to($row);
    991             }
    992         }else{
    993             $row->append($key == 'head' ? [
    994                 $args['show_line_num'] ? ['th', ['style'=>'width:40px;'], '排名'] : '',
    995                 ['th', [], $args['title']],
    996                 ['th', [], '数量'],
    997                 $args['total'] ? ['th', [], '比例'] : ''
    998             ] : [
    999                 $args['show_line_num'] ? ['td', [], $key == 'total' ? '' : $key] : '',
    1000                 ['td', [], $args['label']],
    1001                 ['td', [], $data],
    1002                 $args['total'] ? ['td', [], round($data / $args['total'] * 100, 2).'%'] : ''
     922    public static function bar($data, $args=[]){
     923        return self::line($data, $args, 'Bar');
     924    }
     925
     926    public static function donut($data, $args=[]){
     927        $tag    = wpjam_tag('div', ['class'=>'donut-chart-wrap']);
     928        $data   = wpjam_array($data, fn($k, $v)=> [null, [
     929            'label' => !empty($v['label']) ? ($args['labels'][$v['label']] ?? $v['label']) : '/',
     930            'value' => $v['count']
     931        ]]);
     932
     933        if($args['show_chart'] ?? true){
     934            $tag->append('div', [
     935                'id'    => $args['chart_id'] ?? 'chart_'.wp_generate_password(6, false, false),
     936                'data'  => ['type'=>'Donut', 'chart'=>true, 'options'=>['data'=>$data]]
    1003937            ]);
    1004938        }
    1005939
    1006         return $row;
    1007     }
    1008 
    1009     public static function create_instance(){
     940        if(($args['show_table'] ?? true) && $data){
     941            $columns    = (!empty($args['show_line_num']) ? ['no'=>'排名'] : [])+['label'=>$args['title'] ?? '名称', 'value'=>'数量'];
     942
     943            if($total = $args['total'] ?? false){
     944                $total      = $total === true ? array_sum(array_column($data, 'value')) : $total;
     945                $data       += ['total'=>['label'=>'所有', 'value'=>$total]];
     946                $columns    += ['rate'=>'比例'];
     947            }
     948
     949            $tag->append(wpjam_tag('table', ['wp-list-table', 'widefat', 'striped'])->append([
     950                wpjam_tag('thead')->append(wpjam_tag('tr')->append(wpjam_map($columns, fn($col, $id)=> wpjam_tag('th', ['scope'=>'col', 'id'=>$id], $col)))),
     951                wpjam_tag('tbody')->append(wpjam_map($data, fn($item, $k)=> wpjam_tag('tr')->append(wpjam_map($columns, fn($col, $id)=> wpjam_tag('td', ['data'=>['colname'=>$col]], $item[$id] ?? ($id == 'no' ? ($k === 'total' ? '' : $k+1) : round($item['value']/$total*100, 2).'%'))))))
     952            ]));
     953        }
     954
     955        return $tag;
     956    }
     957
     958    public static function create($args=[]){
    1010959        $offset = (int)get_option('gmt_offset');
    1011960        $offset = $offset >= 0 ? '+'.$offset.':00' : $offset.':00';
     
    1017966        wpjam_script('morris',  wpjam_get_static_cdn().'/morris.js/0.5.1/morris.min.js');
    1018967
    1019         return new self([
     968        return new self($args+[
    1020969            'method'            => 'POST',
    1021970            'show_form'         => true,
     
    1026975        ]);
    1027976    }
    1028 
    1029     public static function get_instance($args=[]){
    1030         static $object;
    1031         return ($object ??= self::create_instance())->update_args(is_array($args) ? $args : []);
    1032     }
    1033977}
  • wpjam-basic/trunk/includes/class-wpjam-api.php

    r3440469 r3454739  
    33    public function call($name, ...$args){
    44        if(is_closure($name)){
    5             $cb     = $name;
     5            $cb = $name;
    66        }else{
    7             $by     = array_find(['model', 'prop'], fn($k)=> str_ends_with($name, '_by_'.$k));
    8             $name   = $by ? explode_last('_by_', $name)[0] : $name;
    9             $cb     = $by == 'prop' ? $this->$name : [$by == 'model' ? $this->model : $this, $name];
     7            $cb = [$this, $name];
     8
     9            if($by = array_find(['model', 'prop'], fn($k)=> str_ends_with($name, '_by_'.$k))){
     10                $name   = explode_last('_by_', $name)[0];
     11                $cb     = $by == 'prop' ? $this->$name : [$this->model, $name];
     12            }
    1013        }
    1114
     
    8588    }
    8689
     90    public function add_items($items){
     91        return wpjam_map($items, [$this, 'add_item'], wp_is_numeric_array($items) ? 'v' : 'kv');
     92    }
     93
    8794    public function remove_item($item, $field=''){
    8895        return $this->process_items(fn($items)=> array_diff($items, [$item]), $field);
     
    104111        $res    = $this->process_items(fn($items)=> wpjam_except($items, $this->prepare_item(null, $key, 'delete', $field) ?? $key), $field);
    105112
    106         is_wp_error($res) || (($cb = wpjam_callback([$this, 'after_delete_item'])) && $cb($key, $field));
     113        is_wp_error($res) || wpjam_call([$this, 'after_delete_item'], $key, $field);
    107114
    108115        return $res;
     
    171178        add_action('plugins_loaded', [$this, 'loaded'], 0);
    172179
    173         add_filter('gettext',               [$this, 'filter_gettext'], 10, 3);
    174         add_filter('gettext_with_context',  [$this, 'filter_gettext'], 10, 4);
    175 
    176         if(get_locale() == 'zh_CN'){
    177             load_textdomain('wpjam', dirname(__DIR__).'/template/wpjam-zh_CN.l10n.php');
    178         }
    179 
    180         if(wpjam_is_json_request()){
    181             ini_set('display_errors', 0);
    182 
    183             remove_filter('the_title', 'convert_chars');
    184 
    185             remove_action('init', 'wp_widgets_init', 1);
    186             remove_action('init', 'maybe_add_existing_user_to_blog');
    187             // remove_action('init', 'check_theme_switched', 99);
    188 
    189             remove_action('plugins_loaded', 'wp_maybe_load_widgets', 0);
    190             remove_action('plugins_loaded', 'wp_maybe_load_embeds', 0);
    191             remove_action('plugins_loaded', '_wp_customize_include');
    192             remove_action('plugins_loaded', '_wp_theme_json_webfonts_handler');
    193 
    194             remove_action('wp_loaded', '_custom_header_background_just_in_time');
    195             remove_action('wp_loaded', '_add_template_loader_filters');
    196         }
     180        wpjam_hooks(['gettext', 'gettext_with_context'], fn($get, ...$args)=> $get === $args[0] ? wpjam_translate(...$args) : $get, 10, 4);
     181
     182        get_locale() == 'zh_CN' && load_textdomain('wpjam', dirname(__DIR__).'/template/wpjam-zh_CN.l10n.php');
     183
     184        wpjam_is_json_request() && wpjam_hooks('remove', [
     185            ['the_title',       'convert_chars'],
     186            ['init',            ['wp_widgets_init', 'maybe_add_existing_user_to_blog']],
     187            ['plugins_loaded',  ['wp_maybe_load_widgets', 'wp_maybe_load_embeds', '_wp_customize_include', '_wp_theme_json_webfonts_handler']],
     188            ['wp_loaded',       ['_custom_header_background_just_in_time', '_add_template_loader_filters']]
     189        ]);
    197190    }
    198191
     
    216209            'src'       => wpjam_get_static_cdn().'/remixicon/4.2.0/remixicon.min.css',
    217210            'method'    => is_admin() ? 'enqueue' : 'register',
    218             'data'      => is_admin() ? "\n".'.wp-menu-image[class*=" ri-"]:before{display:inline-block; line-height:1; font-size:20px;}' : '',
    219211            'priority'  => 1
    220212        ]);
    221213
    222         add_filter('pre_do_shortcode_tag',  fn($pre, $tag)=> [$this->push('shortcode', $tag), $pre][1], 1, 2);
    223         add_filter('do_shortcode_tag',      fn($res, $tag)=> [$this->pop('shortcode'), $res][1], 999, 2);
     214        wpjam_hook('tap', 'pre_do_shortcode_tag',   fn($pre, $tag)=> $this->push('shortcode', $tag), 1, 2);
     215        wpjam_hook('tap', 'do_shortcode_tag',       fn($res, $tag)=> $this->pop('shortcode'), 999, 2);
    224216
    225217        add_action('loop_start',    fn($query)=> $this->push('query', $query), 1);
     
    232224    }
    233225
    234     public function filter_gettext($get, $text, ...$args){
    235         if($get === $text && ($domain = wpjam_at($args, -1)) != 'default'){
    236             $cb = 'translate'.(count($args) >= 2 ? '_with_gettext_context' : '');
    237 
    238             if($domain === 'wpjam' && ($low = strtolower($text)) !== $text && count(explode(' ', $text)) <= 2 && ($trans = $cb($low, ...$args)) != $low){
    239                 return $trans;
    240             }
    241 
    242             return $cb($text, ...array_slice($args, 0, -1));
    243         }
    244 
    245         return $get;
    246     }
    247 
    248226    public function add($field, $key, ...$args){
    249227        [$key, $item]   = $args ? [$key, $args[0]] : [null, $key];
     
    253231        }
    254232
    255         return $this->set($field, $key ?? '[]', $item);
     233        return $this->set($field, $key, $item);
    256234    }
    257235
    258236    public function set($field, $key, ...$args){
    259         $this->data[$field] = is_array($key) ? array_merge(($args && $args[0]) ? $this->get($field) : [], $key) : wpjam_set($this->get($field), $key, ...$args);
     237        $this->data[$field] = is_array($key) ? array_merge(($args && $args[0]) ? $this->get($field) : [], $key) : wpjam_set($this->get($field), $key ?? '[]', ...$args);
    260238
    261239        return is_array($key) ? $key : $args[0];
     
    395373
    396374        if($action){
    397             $value  = is_closure($value) ? wpjam_bind($value, $this) : $value;
    398             $value  ??= is_string($key) ? wpjam_callback([$this->model, 'get_'.$key]) : null;
     375            $value  = is_closure($value) ? wpjam_bind($value, $this) : ($value ?? (is_string($key) ? wpjam_callback([$this->model, 'get_'.$key]) : null));
    399376            $value  = $action === 'callback' ? maybe_callback($value, $this->name) : $value;
    400377        }
     
    444421
    445422    protected function parse_method($name){
    446         if($cb = wpjam_callback([$this->model, $name])){
    447             return $cb;
    448         }
    449 
    450         if($cb = wpjam_callback($this->$name)){
    451             return is_closure($cb) ? wpjam_bind($cb, $this) : $cb;
    452         }
     423        $cb = array_find([[$this->model, $name], $this->$name], fn($v)=> wpjam_callback($v));
     424
     425        return is_closure($cb) ? wpjam_bind($cb, $this) : $cb;
    453426    }
    454427
     
    603576class WPJAM_Register_Group extends WPJAM_Args{
    604577    public function get_objects($args=[], $operator='AND'){
    605         $this->defaults && array_map([$this, 'by_default'], array_keys($this->defaults));
     578        $this->defaults && wpjam_map($this->defaults, [$this, 'by_default'], 'k');
    606579
    607580        $objects    = wpjam_filter($this->get_arg('objects[]'), $args, $operator);
     
    771744            $cb ? $cb($this->name, $values) : [$this, 'update_'.$fix]($values);
    772745        }else{
    773             wpjam_map($values, fn($v, $k)=> $submit == 'reset' ? ('delete_'.$fix)($k) : ('update_'.$fix)($k, $v));
     746            wpjam_map($values, ...($submit == 'reset' ? ['delete_'.$fix, 'k'] : ['update_'.$fix, 'kv']));
    774747        }
    775748
     
    888861        }elseif($key == 'admin_load'){
    889862            $value  = wp_is_numeric_array($value) ? $value : ($value ? [$value] : []);
    890             $value  = array_map(fn($v) => ($this->model && !isset($v['callback']) && !isset($v['model'])) ? $v+['model'=>$this->model] : $v, $value);
     863            $value  = array_map(fn($v)=> ($this->model && !isset($v['callback']) && !isset($v['model'])) ? $v+['model'=>$this->model] : $v, $value);
    891864        }elseif($key == 'sections'){
    892865            if(!$value || !is_array($value)){
     
    895868            }
    896869
    897             $value  = wpjam_array($value, fn($k, $v)=> is_array($v) && isset($v['fields']) ? [$k, wpjam_set($v, 'fields', maybe_callback($v['fields'] ?? [], $k, $this->name))] : null);
     870            $value  = wpjam_array($value, fn($k, $v)=> is_array($v) && isset($v['fields']) ? [$k, ['fields'=>maybe_callback($v['fields'] ?? [], $k, $this->name)]+$v] : null);
    898871        }
    899872
     
    10281001    }
    10291002
    1030     protected static function call_setting($action, ...$args){
    1031         return ($object = self::get_object()) ? [$object, $action.'_setting'](...$args) : null;
    1032     }
    1033 
    1034     public static function get_setting($name='', ...$args){
    1035         return self::call_setting('get', $name, ...$args);
    1036     }
    1037 
    1038     public static function update_setting(...$args){
    1039         return self::call_setting('update', ...$args);
    1040     }
    1041 
    1042     public static function delete_setting($name){
    1043         return self::call_setting('delete', $name);
     1003    public static function __callStatic($method, $args){
     1004        return ($object = self::get_object()) ? [$object, $method](...$args) : null;
    10441005    }
    10451006}
     
    11851146            $data   = $wpdb->get_results("SELECT * FROM {$table} WHERE {$where}", ARRAY_A) ?: [];
    11861147
    1187             return $data && $column ? array_first($data)[$this->get_column($column)] : array_map(fn($v)=> wpjam_set($v, 'meta_value', maybe_unserialize($v['meta_value'])), $data);
     1148            return $data && $column ? array_first($data)[$this->get_column($column)] : array_map(fn($v)=> ['meta_value'=>maybe_unserialize($v['meta_value'])]+$v, $data);
    11881149        }
    11891150
     
    13641325        if($this->modules){
    13651326            $modules    = maybe_callback($this->modules, $this->name, $this->args);
    1366             $results    = array_map(fn($module)=> WPJAM_JSON_Module::parse($module), wp_is_numeric_array($modules) ? $modules : [$modules]);
     1327            $results    = array_map(['WPJAM_JSON_Module', 'parse'], wp_is_numeric_array($modules) ? $modules : [$modules]);
    13671328        }elseif($this->callback){
    13681329            $fields     = wpjam_try('maybe_callback', $this->fields ?: [], $this->name);
     
    14081369            exit;
    14091370        }
     1371
     1372        ini_set('display_errors', 0);
    14101373
    14111374        add_filter('wp_die_'.(array_find(['jsonp_', 'json_'], fn($v)=> call_user_func('wp_is_'.$v.'request')) ?: '').'handler', fn()=> [self::class, 'die_handler']);
  • wpjam-basic/trunk/includes/class-wpjam-field.php

    r3436010 r3454739  
    4242
    4343    public function class($action='', ...$args){
    44         $args   = array_map(fn($v)=> wp_parse_list($v ?: []), [$this->class, ...$args]);
     44        $args   = array_map('wp_parse_list', [$this->class ?: [], ...$args]);
    4545        $cb     = $action ? ['add'=>'array_merge', 'remove'=>'array_diff', 'toggle'=>'wpjam_toggle'][$action] : '';
    4646
     
    108108
    109109                return self::is_bool($v) ? [$v, $v] : null;
    110             }else{
    111                 return self::is_bool($k) ? ($v ? [$k, $k] : null) : [$k, $v];
    112             }
     110            }
     111
     112            return self::is_bool($k) ? ($v ? [$k, $k] : null) : [$k, $v];
    113113        });
    114114    }
     
    217217
    218218        if($type == '_data_type'){
    219             if(str_ends_with($action, '_value')){
     219            if(in_array($action, ['parse', 'validate'])){
    220220                if(!$this->$type){
    221221                    return $args[0];
     
    223223
    224224                if($this->multiple && is_array($args[0])){
    225                     return array_map(fn($v)=> wpjam_try([$this, $method], $v), $args[0]);
    226                 }
    227             }
    228 
    229             array_push($args, $this);
     225                    return array_map([$this, $method], $args[0]);
     226                }
     227
     228                $args   = [$action, ...$args];
     229                $action = 'with_field';
     230            }
     231
     232            if($action != 'get_arg'){
     233                $args[] = $this;
     234            }
    230235        }elseif($type == '_fields'){
    231236            $this->$type    ??= WPJAM_Fields::create($this->fields, $this->_fields_args ?: [], $this);
     
    284289        }
    285290
    286         $value  = array_filter(['type'=>$this->get_arg('show_in_rest.type')])+($this->get_schema_by_data_type() ?: []);
     291        $value  = array_filter(['type'=>$this->get_arg('show_in_rest.type')])+($this->get_arg_by_data_type('schema') ?: []);
    287292
    288293        if($this->is('mu')){
     
    308313
    309314            $value  += ['type'=>'string'];
    310             $value  += array_filter(wpjam_map(((array_fill_keys(['integer', 'number'], ['minimum'=>'min', 'maximum'=>'max'])+[
     315            $value  += wpjam_array((array_fill_keys(['integer', 'number'], ['minimum'=>'min', 'maximum'=>'max'])+[
    311316                'array'     => ['maxItems'=>'max_items', 'minItems'=>'min_items', 'uniqueItems'=>'unique_items'],
    312317                'string'    => ['minLength'=>'minlength', 'maxLength'=>'maxlength'],
    313             ])[$value['type']] ?? []), fn($v)=> $this->$v), fn($v)=> !is_blank($v));
     318            ])[$value['type']] ?? [], fn($k, $v)=> is_blank($this->$v) ? null : [$k, $this->$v]);
     319
    314320        }
    315321
     
    317323            foreach(wpjam_pull($value, ['enum', 'items', 'properties']) as $k => $v){
    318324                if($k == 'enum'){
    319                     $value[$k]  = array_map(fn($iv)=> $this->sanitize($iv, $value), $v);
     325                    $value[$k]  = array_map(fn($i)=> $this->sanitize($i, $value), $v);
    320326                }elseif($value['type'] == ($k == 'items' ? 'array' : 'object')){
    321327                    $value[$k]  = $k == 'items' ? $parse($v) : array_map($parse, $v);
     
    437443                }
    438444
    439                 $value && ($value = $this->validate_value_by_data_type($value));
     445                $value && ($value = $this->validate_by_data_type($value));
    440446            }
    441447
     
    480486        $args   = $this->$k ? [[$k=>$this->$k]+wpjam_pick($args, ['id']), array_last($this->_names)] : [$args, $this->_names];
    481487
    482         return wpjam_value_callback(...$args) ?? $this->value;
     488        return wpjam_value(...$args) ?? $this->value;
    483489    }
    484490
     
    521527        }
    522528
    523         return $value && $this->parse_required ? $this->parse_value_by_data_type($value) : $value;
     529        return $value && $this->parse_required ? $this->parse_by_data_type($value) : $value;
    524530    }
    525531
     
    623629            }elseif($this->is('mu-text')){
    624630                if(($this->item_type ??= 'text') == 'text'){
    625                     $this->direction == 'row' && ($this->class  ??= 'medium-text');
    626 
    627                     array_walk($value, fn(&$v)=> ($l = $this->query_label_by_data_type($v)) && ($v = ['value'=>$v, 'label'=>$l]));
     631                    $this->direction == 'row' && ($this->class ??= 'medium-text');
     632
     633                    $value  = $this->query_label_by_data_type($value) ?: $value;
    628634                }
    629635
     
    689695        }elseif($this->is('uploader')){
    690696            $mimes  = wpjam_accept_to_mime_types($this->accept ?: 'image/*');
    691             $exts   = implode(',', array_map(fn($v)=> str_replace('|', ',', $v), array_keys($mimes)));
     697            $exts   = implode(',', wpjam_map($mimes, fn($v)=> str_replace('|', ',', $v), 'k'));
    692698            $params = ['_ajax_nonce'=>wp_create_nonce('upload-'.$this->key), 'action'=>'wpjam-upload', 'name'=>$this->key, 'mimes'=>$mimes];
    693699
     
    719725    }
    720726
    721     public static function parse($field){
     727    public static function parse($field, ...$args){
     728        if(is_object($field)){
     729            $object = $field;
     730            $key    = array_shift($args);
     731
     732            if(is_array($key)){
     733                return wpjam_reduce($key, fn($c, $v, $k)=> wpjam_field($object, $k, $v), $object);
     734            }
     735
     736            if(!$args && is_callable($key)){
     737                [$args, $key]   = [[$key], ''];
     738            }
     739
     740            return [$object, $args ? 'update_arg' : 'delete_arg']('_fields['.$key.']', ...$args);
     741        }
     742
    722743        $field  = is_string($field) ? ['type'=>'view', 'value'=>$field, 'wrap_tag'=>''] : parent::parse($field);
    723744        $field  = ['options'=>(($field['options'] ?? []) ?: [])]+$field;
     
    808829
    809830            if($method == 'validate'){
    810                 $can        = !$field->disabled && !$field->readonly && !$field->is('view');
     831                $can        = !$field->disabled && !$field->readonly && !$field->is('view, button');
    811832                $args[0]    = $flat ? $values : $field->unpack($values);
    812833
     
    875896    }
    876897
    877     public static function create($fields, $args=[], $parent=null){
     898    public function process($items, $args=[]){
     899        $sumable    = $this->sumable;
     900
     901        if(!$sumable){
     902            $sumable    = [1=>[], 2=>[]];
     903
     904            foreach($this->fields as $k => $v){
     905                if($s = $v['sumable'] ?? ''){
     906                    $sumable[$s][$k]    = 0;
     907                }
     908
     909                if(array_filter($f = [$v['format'] ?? '', $v['precision'] ?? null])){
     910                    $formats[$k]    = $f;
     911                }
     912
     913                if(($e = $v['if_error'] ?? '') || is_numeric($e)){
     914                    $if_errors[$k]  = $e;
     915                }
     916            }
     917
     918            $formulas   = wpjam_formula($this->fields);
     919            $sumable[2] = array_intersect_key($formulas, $sumable[2]);
     920
     921            $this->update_args([
     922                'formulas'  => $formulas,
     923                'sumable'   => $sumable,
     924                'formats'   => $formats ?? [],
     925                'if_errors' => $if_errors ?? [],
     926            ]);
     927        }
     928
     929        $sum    = $args['sum'] ?? true;
     930        $calc   = $args['calc'] ?? null;
     931
     932        if($sum === 'accumulate'){
     933            $calc   ??= false;
     934            $field  = $args['field'] ?? '';
     935            $to     = $args['to'] ?? [];
     936            $to     = $field ? $to : ($to ?: $sumable[1]);
     937        }else{
     938            $calc   ??= true;
     939            $sums   = $sum ? $sumable[1] : null;
     940        }
     941
     942        foreach($items as $i => &$item){
     943            if($calc && $item && is_array($item)){
     944                $item   = wpjam_calc($item, ($calc === 'sum' ? $sumable[2] : $this->formulas), $this->if_errors);
     945            }
     946
     947            if(!empty($args['filter']) && !wpjam_matches($item, $args['filter'])){
     948                unset($items[$i]); continue;
     949            }
     950
     951            if($sum === 'accumulate'){
     952                if($field){
     953                    $g      = $item[$field] ?? '';
     954                    $to[$g] ??= $sumable[1]+$item;
     955                    $target = &$to[$g];
     956                }else{
     957                    $target = &$to;
     958                }
     959            }elseif($sum){
     960                $target = &$sums;
     961            }
     962
     963            if($sum){
     964                foreach($sumable[1] as $k => $null){
     965                    $target[$k] += wpjam_format($item[$k] ?? 0, '-,', 0);
     966                }
     967            }
     968
     969            if(!empty($args['format'])){
     970                $item   = wpjam_format($item, $this->formats);
     971            }
     972        }
     973
     974        if($sum === 'accumulate'){
     975            return $to;
     976        }
     977
     978        if(!empty($args['orderby'])){
     979            $items  = wpjam_sort($items, $args['orderby'], $args['order']);
     980        }
     981
     982        if($sums){
     983            $sums   = wpjam_calc($sums, $sumable[2], $this->if_errors)+(is_array($sum) ? $sum : []);
     984            $items  = wpjam_add_at($items, 0, '__sum__', (!empty($args['format']) ? wpjam_format($sums, $this->formats) : $sums));
     985        }
     986
     987        return $items;
     988    }
     989
     990    public static function create($fields, $type='', ...$args){
     991        if($type === 'processor'){
     992            return new self(['fields'=>$fields]);
     993        }
     994
     995        $parent = $args[0] ?? null;
     996        $args   = $type ?: [];
    878997        $prop   = $parent && !$parent->is('flat');
    879998        $attr   = ['_parent'=>$parent, '_fields_args'=>$args]+wpjam_pick($parent ?: [], ['readonly', 'disabled']);
     
    8921011    }
    8931012
    894     public static function parse($fields, $flat=false, $prefix=''){
     1013    public static function parse($fields, ...$args){
     1014        if(is_object($fields)){
     1015            $object = $fields;
     1016            $fields = [];
     1017
     1018            foreach($object->get_arg('_fields[]') as $key => $field){
     1019                if(is_callable($field)){
     1020                    $result = wpjam_try($field, ...$args);
     1021
     1022                    if(is_numeric($key)){
     1023                        $fields = array_merge($fields, $result);
     1024                    }else{
     1025                        $fields[$key]   = $result;
     1026                    }
     1027                }elseif(wpjam_is_assoc_array($field)){
     1028                    $fields[$key]   = $field;
     1029                }
     1030            }
     1031
     1032            return $fields;
     1033        }
     1034
     1035        [$flat, $prefix]    = $args+[false, ''];
     1036
    8951037        foreach($fields as $key => $field){
    8961038            $field  = WPJAM_Field::parse($field);
     
    10881230
    10891231                    foreach($pks as $pk => [$op, $fix]){
    1090                         if(wpjam_array($platforms, fn($pf)=> $pf->has_path($name, $strict && $op == 'OR'), $op)){
     1232                        if(wpjam_matches($platforms, fn($pf)=> $pf->has_path($name, $strict && $op == 'OR'), $op)){
    10911233                            $i++;
    10921234
     
    11741316#[config(model:false)]
    11751317class WPJAM_Data_Type extends WPJAM_Register{
    1176     public function __call($method, $args){
    1177         if(in_array($method, ['parse_value', 'validate_value'])){
    1178             [$value, $field]    = $args;
    1179 
    1180             $action = wpjam_at($method, '_', 0);
    1181             $result = $this->$method ? $this->call($method.'_by_prop', $value, $field) : (($cb = $this->model.'::with_field') && wpjam_callback($cb) ? wpjam_try($cb, $action, $field, $value) : $value);
    1182 
    1183             return $action == 'validate' && is_null($result) ? wpjam_throw('invalid_field_value', $field->_title.'的值无效') : $result;
    1184         }elseif($method == 'get_path'){
    1185             return ($cb = $this->model.'::get_path') && wpjam_callback($cb) ? wpjam_try($cb, ...$args) : null;
    1186         }elseif($method == 'get_schema'){
    1187             return $this->get_arg('schema');
    1188         }
    1189 
    1190         trigger_error($method);
     1318    public function get_path($args, $item){
     1319        return wpjam_if_error(wpjam_call($this->model.'::get_path', $args, $item), 'throw');
     1320    }
     1321
     1322    public function with_field($action, $value, $field){
     1323        $method = $action.'_value';
     1324        $result = $this->$method ? $this->call($method.'_by_prop', $value, $field) : (($cb = $this->model.'::with_field') && wpjam_callback($cb) ? wpjam_try($cb, $action, $field, $value) : $value);
     1325
     1326        return $action == 'validate' && is_null($result) ? wpjam_throw('invalid_field_value', $field->_title.'「'.$value.'」的值无效') : $result;
     1327    }
     1328
     1329    public function query_label($value){
     1330        if($value && $this->model && $this->label_field){
     1331            if(is_array($value)){
     1332                wpjam_call($this->model.'::update_caches', $value);
     1333
     1334                return array_map(fn($v)=> ($l = $this->query_label($v)) ? ['label'=>$l, 'value'=>$v] : $v, $value);
     1335            }
     1336
     1337            return ($this->model::get($value) ?: [])[$this->label_field] ?? null;
     1338        }
    11911339    }
    11921340
     
    12031351            $items  = wp_is_numeric_array($result) ? $result : ($result['items'] ?? []);
    12041352
    1205             return $this->label_field ? array_map(fn($v)=> [
    1206                 'label' => wpjam_get($v, $this->label_field),
    1207                 'value' => wpjam_get($v, $this->id_field)
    1208             ], $items) : $items;
     1353            return $this->label_field ? wpjam_column($items, ['label'=>$this->label_field, 'value'=>$this->id_field]) : $items;
    12091354        }
    12101355
    12111356        return [];
    1212     }
    1213 
    1214     public function query_label($id, $field=null){
    1215         if($this->query_label){
    1216             return $id ? $this->call('query_label_by_prop', $id, $field) : null;
    1217         }elseif($this->model && $this->label_field){
    1218             return ($id ? ($this->model::get($id) ?: []) : [])[$this->label_field] ?? null;
    1219         }
    12201357    }
    12211358
     
    12551392
    12561393                $args['label_field']    ??= wpjam_pull($args, 'label_key') ?: 'title';
    1257                 $args['id_field']       ??= wpjam_pull($args, 'id_key') ?: wpjam_call($model.'::get_primary_key');
     1394                $args['id_field']       ??= wpjam_pull($args, 'id_key') ?: wpjam_value($model, 'primary_key');
    12581395
    12591396                $object = $object->get_sub($model) ?: $object->register_sub($model, $args+[
    1260                     'meta_type'         => wpjam_call($model.'::get_meta_type') ?: '',
     1397                    'meta_type'         => wpjam_value($model, 'meta_type') ?: '',
    12611398                    'validate_value'    => fn($v)=> wpjam_try([$model, 'get'], $v) ? $v : null
    12621399                ]);
     
    12761413        $args   = $type ? (['data_type'=>$type]+(in_array($type, ['post_type', 'taxonomy']) ? [$type => wpjam_get($args, $type, '')] : [])) : [];
    12771414
    1278         return $output == 'key' ? ($args ? '__'.md5(serialize(array_map(fn($v)=> is_closure($v) ? spl_object_hash($v) : $v, $args))) : '') : $args;
     1415        return $output == 'key' ? ($args ? '__'.md5(wpjam_serialize($args)) : '') : $args;
    12791416    }
    12801417
     
    12831420    }
    12841421}
    1285 
    1286 class WPJAM_Data_Processor extends WPJAM_Args{
    1287     public function __construct($fields){
    1288         $this->args = ['fields'=>$fields, 'sumable'=>[1=>[], 2=>[]]];
    1289 
    1290         foreach($fields as $k => $v){
    1291             if(!empty($v['sumable'])){
    1292                 $this->update_arg('sumable['.$v['sumable'].'][]', $k);
    1293             }
    1294 
    1295             if(($if = $v['if_error'] ?? '') || is_numeric($if)){
    1296                 $this->update_arg('if_errors['.$k.']', $if);
    1297             }
    1298 
    1299             $format = [$v['format'] ?? '', $v['precision'] ?? null];
    1300 
    1301             if(array_filter($format)){
    1302                 $this->update_arg('formats['.$k.']', $format);
    1303             }
    1304         }
    1305     }
    1306 
    1307     public function formulas($key='', $path=[]){
    1308         if($key){
    1309             $fields     = $this->fields;
    1310             $throw      = fn($key, $msg)=> wpjam_throw('invalid_formula', implode([
    1311                 is_array($key) ? $msg.':' : '',
    1312                 implode(' → ', wpjam_map((array)$key, fn($k)=> '字段'.($fields[$k]['title'] ?? '').'「'.$k.'」'.'公式「'.$fields[$k]['formula'].'」')),
    1313                 is_array($key) ? '' : ','.$msg
    1314             ]));
    1315 
    1316             $path[]     = in_array($key, $path) ? $throw(array_slice($path, array_search($key, $path)), '公式嵌套') : $key;
    1317             $depth      = 0;
    1318             $functions  = ['abs', 'ceil', 'pow', 'sqrt', 'pi', 'max', 'min', 'fmod', 'round'];
    1319             $signs      = ['+', '-', '*', '/', '(', ')', ',', '%'];
    1320             $formula    = preg_split('/\s*(['.preg_quote(implode($signs), '/').'])\s*/', trim($fields[$key]['formula']), -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    1321 
    1322             foreach($formula as $t){
    1323                 if(is_numeric($t)){
    1324                     str_ends_with($t, '.') && $throw($key, '无效数字「'.$t.'」');
    1325                 }elseif(str_starts_with($t, '$')){
    1326                     isset($fields[substr($t, 1)]) || $throw($key, '「'.$t.'」未定义');
    1327                 }elseif($t == '('){
    1328                     $depth  += 1;
    1329                 }elseif($t == ')'){
    1330                     $depth  -= $depth ? 1 : $throw($key, '括号不匹配');
    1331                 }else{
    1332                     in_array($t, $signs) || in_array(strtolower($t), $functions) || $throw($key, '无效的「'.$t.'」');
    1333                 }
    1334             }
    1335 
    1336             $depth && $throw($key, '括号不匹配');
    1337 
    1338             wpjam_map($formula, fn($t)=> try_remove_prefix($t, '$') && !empty($fields[$t]['formula']) && $this->formulas($t, $path));
    1339 
    1340             $this->update_arg('formulas['.array_pop($path).']', $formula);
    1341         }else{
    1342             is_null($this->formulas) && wpjam_map($this->fields, fn($v, $k)=> empty($v['formula']) || $this->get_arg('formulas['.$k.']') || $this->formulas($k, []));
    1343 
    1344             return $this->formulas;
    1345         }
    1346     }
    1347 
    1348     private function number($v){
    1349         $v  = is_string($v) ? str_replace(',', '', trim($v)) : $v;
    1350 
    1351         return is_numeric($v) ? $v : false;
    1352     }
    1353 
    1354     public function process($items, $args=[]){
    1355         $args   = wp_parse_args($args, ['calc'=>true, 'sum'=>true, 'format'=>false, 'orderby'=>'', 'order'=>'', 'filter'=>'']);
    1356         $sums   = $args['sum'] ? array_fill_keys($this->sumable[1], 0) : [];
    1357 
    1358         foreach($items as $i => &$item){
    1359             if($args['calc']){
    1360                 $item   = $this->calc($item);
    1361             }
    1362 
    1363             if($args['filter'] && !wpjam_matches($item, $args['filter'])){
    1364                 unset($items[$i]); continue;
    1365             }
    1366 
    1367             if($args['sum']){
    1368                 $sums   = wpjam_map($sums, fn($v, $k)=> $v+$this->number($item[$k] ?? 0));
    1369             }
    1370 
    1371             if($args['format']){
    1372                 $item   = $this->format($item);
    1373             }
    1374         }
    1375 
    1376         if($args['orderby']){
    1377             $items  = wpjam_sort($items, $args['orderby'], $args['order']);
    1378         }
    1379 
    1380         if($args['sum']){
    1381             $sums   = $this->calc($sums, ['sum'=>true])+(is_array($args['sum']) ? $args['sum'] : []);
    1382             $items  = wpjam_add_at($items, 0, '__sum__', ($args['format'] ? $this->format($sums) : $sums));
    1383         }
    1384 
    1385         return $items;
    1386     }
    1387 
    1388     public function calc($item, $args=[]){
    1389         if(!$item || !is_array($item)){
    1390             return $item;
    1391         }
    1392 
    1393         if(!empty($args['formula'])){
    1394             foreach($args['formula'] as &$t){
    1395                 if(str_starts_with($t, '$')){
    1396                     $k  = substr($t, 1);
    1397                     $v  = $item[$k] ?? null;
    1398                     $r  = isset($v) ? $this->number($v) : false;
    1399 
    1400                     if($r !== false){
    1401                         $t  = (float)$r;
    1402                         $t  = $t < 0 ? '('.$t.')' : $t;
    1403                     }else{
    1404                         $t  = $this->get_arg('if_errors['.$k.']');
    1405 
    1406                         if(!isset($t)){
    1407                             return $args['if_error'] ?? (isset($v) ? '!!无法计算' : '!无法计算');
    1408                         }
    1409                     }
    1410                 }
    1411             }
    1412 
    1413             try{
    1414                 return eval('return '.implode($args['formula']).';');
    1415             }catch(DivisionByZeroError $e){
    1416                 return $args['error'] ?? '!除零错误';
    1417             }catch(throwable $e){
    1418                 return $args['error'] ?? '!计算错误:'.$e->getMessage();
    1419             }
    1420         }
    1421 
    1422         $formulas   = $this->formulas();
    1423         $formulas   = empty($args['sum']) ? $formulas : wpjam_pick($formulas, $this->sumable[2]);
    1424 
    1425         if($formulas){
    1426             $prev   = set_error_handler(function($no, $str){
    1427                 throw str_contains($str , 'Division by zero') ? new DivisionByZeroError($str) : new ErrorException($str, $no);
    1428             });
    1429 
    1430             $item   = array_diff_key($item, $formulas);
    1431             $item   = wpjam_reduce($formulas, fn($c, $v, $k)=> wpjam_set($c, $k, $this->calc($c, ['error'=>$this->get_arg('if_errors['.$k.']'), 'formula'=>$v])), $item);
    1432 
    1433             $prev ? set_error_handler($prev) : restore_error_handler();
    1434         }
    1435 
    1436         return $item;
    1437     }
    1438 
    1439     public function sum($items, $args=[]){
    1440         return ($this->sumable && $items) ? $this->calc(wpjam_at($this->process($items, $args+['sum'=>true]), 0), ['sum'=>true]) : [];
    1441     }
    1442 
    1443     public function accumulate($to, $items, $args=[]){
    1444         $args   = wp_parse_args($args, ['calc'=>true, 'field'=>'', 'filter'=>'']);
    1445         $keys   = $this->sumable[1];
    1446 
    1447         if(!$args['field']){
    1448             $group  = '____';
    1449             $to     = [$group=>($to ?: array_fill_keys($keys, 0))];
    1450         }
    1451 
    1452         foreach($items as $item){
    1453             if($args['calc']){
    1454                 $item   = $this->calc($item);
    1455             }
    1456 
    1457             if($args['filter'] && !wpjam_matches($item, $args['filter'])){
    1458                 continue;
    1459             }
    1460 
    1461             if($args['field']){
    1462                 $group  = $item[$args['field']] ?? '';
    1463             }
    1464 
    1465             $exists = isset($to[$group]);
    1466 
    1467             if(!$exists){
    1468                 $to[$group] = $item;
    1469             }
    1470 
    1471             foreach($keys as $k){
    1472                 $to[$group][$k] = ($exists ? $to[$group][$k] : 0)+($this->number($item[$k] ?? 0) ?: 0);
    1473             }
    1474         }
    1475 
    1476         return $args['field'] ? $to : $to[$group];
    1477     }
    1478 
    1479     public function format($item){
    1480         return wpjam_reduce($this->formats ?: [], fn($c, $v, $k)=> isset($c[$k]) && is_numeric($c[$k]) ? wpjam_set($c, $k, wpjam_format($c[$k], ...$v)) : $c, $item);
    1481     }
    1482 }
  • wpjam-basic/trunk/includes/class-wpjam-list-table.php

    r3440469 r3454739  
    88
    99    public function __construct($args=[]){
    10         add_screen_option('list_table', ($GLOBALS['wpjam_list_table']   = wpjam_admin('list_table', $this)));
    11 
    12         wp_doing_ajax() && wpjam_get_post_parameter('action_type') == 'query_items' && ($_REQUEST   = $this->get_data()+$_REQUEST); // 兼容
     10        add_screen_option('list_table', ($GLOBALS['wpjam_list_table'] = wpjam_admin('list_table', $this)));
     11
     12        wp_doing_ajax() && wpjam_get_post_parameter('action_type') == 'query_items' && ($_REQUEST = $this->get_data()+$_REQUEST);
    1313
    1414        $this->screen   = $screen = get_current_screen();
     
    1616
    1717        array_map([$this, 'component'], ['action', 'view', 'column']);
    18 
    19         wpjam_admin('style', $this->style);
    20         wpjam_admin('vars[list_table]', fn()=> $this->get_setting());
    21         wpjam_admin('vars[page_title_action]', fn()=> $this->get_action('add', ['class'=>'page-title-action']) ?: '');
    22 
    23         add_filter('views_'.$screen->id, [$this, 'filter_views']);
    24         add_filter('bulk_actions-'.$screen->id, [$this, 'filter_bulk_actions']);
    25         add_filter('manage_'.$screen->id.'_sortable_columns', [$this, 'filter_sortable_columns']);
    26 
    27         $this->builtin ? $this->page_load() : parent::__construct($this->_args);
     18        array_map(fn($k)=> add_filter('manage_'.$screen->id.'_'.$k, [$this, 'filter_'.$k]), ['columns', 'sortable_columns']);
     19        array_map(fn($k, $s)=> add_filter($k.$s.$screen->id, [$this, 'filter_'.$k]), ['views', 'bulk_actions'], ['_', '-']);
     20
     21        wpjam_admin([
     22            'style'                     => $this->style,
     23            'vars[list_table]'          => fn()=> $this->get_setting(),
     24            'vars[page_title_action]'   => fn()=> $this->get_action('add', ['class'=>'page-title-action']) ?: ''
     25        ]);
     26
     27        if($this->builtin){
     28            $this->page_load();
     29        }else{
     30            add_filter('list_table_primary_column', fn()=> $this->primary_column ??= array_key_first(wpjam_except($this->columns, ['no', 'cb'])));
     31
     32            parent::__construct($this->_args);
     33        }
    2834    }
    2935
     
    3743        }
    3844
    39         if(in_array($name, ['primary_key', 'actions', 'views', 'fields', 'filterable_fields', 'searchable_fields'])){
    40             $value  = wpjam_trap(in_array($name, ['actions', 'views', 'fields']) ? [$this, 'get_'.$name.'_by_model'] : $this->model.'::get_'.$name, []) ?: [];
     45        if(in_array($name, ['actions', 'views', 'fields'])){
     46            $value  = [$this, 'get_'.$name.'_by_model']();
     47
     48            if($name == 'fields'){
     49                return $this->$name = wpjam_fields($value ?: [], true);
     50            }
     51
     52            return $value ?? ($name == 'actions' ? ($this->builtin ? [] : WPJAM_Model::get_actions()) : ($this->views_by_model() ?: []));
     53        }elseif(in_array($name, ['primary_key', 'filterable_fields', 'searchable_fields'])){
     54            $value  = wpjam_trap($this->model.'::get_'.$name, []) ?: [];
    4155
    4256            if($name == 'primary_key'){
    4357                return $this->$name = $value ?: 'id';
    44             }elseif($name == 'fields'){
    45                 return $this->$name = wpjam_fields($value, true);
    4658            }elseif($name == 'filterable_fields'){
    4759                $fields = wpjam_filter($this->fields, ['filterable'=>true]);
    4860                $views  = array_keys(wpjam_filter($fields, ['type'=>'view']));
    49                 $fields = wpjam_map(wpjam_except($fields, $views), fn($v)=> wpjam_except($v, ['title', 'before', 'after', 'required', 'show_admin_column'])+[($v['type'] === 'select' ? 'show_option_all' : 'placeholder') => $v['title'] ?? '']);
     61                $fields = array_map(fn($v)=> wpjam_except($v, ['title', 'before', 'after', 'required', 'show_admin_column'])+[($v['type'] === 'select' ? 'show_option_all' : 'placeholder') => $v['title'] ?? ''], wpjam_except($fields, $views));
    5062
    5163                return $this->$name = $fields+($fields && !$this->builtin && $this->sortable_columns ? [
    52                     'orderby'   => ['options'=>[''=>'排序']+wpjam_map(array_intersect_key($this->columns, $this->sortable_columns), 'wp_strip_all_tags')],
     64                    'orderby'   => ['options'=>[''=>'排序']+array_map('wp_strip_all_tags', array_intersect_key($this->columns, $this->sortable_columns))],
    5365                    'order'     => ['options'=>['desc'=>'降序','asc'=>'升序']]
    5466                ] : [])+array_fill_keys(array_merge($value, $views), []);
     
    5971            $left   = $name == 'left_data';
    6072            $fields = $left ? $this->left_fields : array_filter($this->filterable_fields);
    61             $data   = ($name == 'form_data' || wp_doing_ajax()) ? wpjam_get_post_parameter($left ? 'params' : $name) : wpjam_get_parameter();
    62             $value  = $data && $fields ? wpjam_trap([wpjam_fields($fields), 'validate'], wp_parse_args($data), []) : [];
    63 
    64             return $this->$name = array_filter($value, fn($v)=> isset($v) && $v !== [])+($left ? [] : (wpjam_admin('chart', 'get_data', ['data'=>$data]) ?: []));
     73            $data   = $name == 'form_data' || wp_doing_ajax() ? wpjam_get_post_parameter($left ? 'params' : $name) : wpjam_get_parameter();
     74
     75            return array_filter(array_merge(...array_map(fn($v)=> $v ? wpjam_trap([$v, 'validate'], wp_parse_args($data), []) : [], [
     76                $data && $fields ? wpjam_fields($fields) : '',
     77                $left ? '' : wpjam_chart()
     78            ])), fn($v)=> isset($v) && $v !== []);
    6579        }elseif($name == 'offset'){
    6680            return ((int)$this->per_page ?: 50)*($this->get_pagenum()-1);
     
    6983
    7084    public function __set($name, $value){
    71         return in_array($name, $this->compat_fields, true) ? ($this->$name  = $value) : ($this->_args[$name]    = $value);
     85        return in_array($name, $this->compat_fields, true) ? ($this->$name = $value) : ($this->_args[$name] = $value);
    7286    }
    7387
     
    8296            return wpjam_get_data_parameter(...$args);
    8397        }elseif($method == 'get_date'){
    84             $type   = array_find(['prev', 'next', 'current'], fn($v)=> in_array($v, $args, true));
    85 
    86             if($type == 'current'){
    87                 [$year, $month] = array_map('wpjam_date', ['Y', 'm']);
    88             }else{
    89                 $year   = clamp((int)$this->get_data('year') ?: wpjam_date('Y'), 1970, 2200);
    90                 $month  = clamp((int)$this->get_data('month') ?: wpjam_date('m'), 1, 12);
    91                 $month  += $type ? ($offset = $type == 'prev' ? -1 : 1) : 0;
    92 
    93                 if(in_array($month, [0, 13])){
    94                     $year   += $offset;
    95                     $month  = abs($month-12);
    96                 }
     98            $type   = $args[0] ?? '';
     99
     100            [$year, $month] = array_map(fn($k, $f, $r)=> clamp(($type == 'current' ? 0 : (int)$this->get_data($k)) ?: wpjam_date($f), ...$r), ['year', 'month'], ['Y', 'm'], [[1970, 2200], [1, 12]]);
     101
     102            $offset = $type == 'prev' ? -1 : ($type == 'next' ? 1 : 0);
     103            $month  += $offset;
     104
     105            if(in_array($month, [0, 13])){
     106                $year   += $offset;
     107                $month  = abs($month-12);
    97108            }
    98109
     
    103114            return $args[0];
    104115        }elseif(try_remove_suffix($method, '_by_model')){
    105             return ($cb = wpjam_callback([$this->model, $method])) ? wpjam_catch($cb, ...$args) : ($method == 'get_actions' ? ($this->builtin ? [] : WPJAM_Model::get_actions()) : ($method == 'get_views' ? $this->views_by_model() : null));
     116            return ($cb = wpjam_callback([$this->model, $method])) ? wpjam_catch($cb, ...$args) : null;
     117        }elseif(try_remove_suffix($method, '_row_actions')){
     118            [$value, $id]   = $method == 'get' ? [[], $args[0]] : [$args[0], $this->parse_id($args[1])];
     119
     120            $names  = array_diff($this->row_actions ?: [], $this->next_actions ?: []);
     121            $value  += $this->get_action($names, ['id'=>$id]+($this->layout == 'calendar' ? ['wrap'=>'<span class="%s"></span>'] : []));
     122            $value  += $this->builtin ? wpjam_pull($value, ['view', 'delete', 'trash', 'spam', 'remove']) : [];
     123
     124            return wpjam_except($value+($this->builtin || $this->primary_key == 'id' ? ['id'=>'ID: '.$id] : []), wpjam_admin('removed_actions[]'));
    106125        }elseif(try_remove_prefix($method, 'ob_get_')){
    107             return wpjam_ob([$this, $method], ...$args);
     126            $result = wpjam_ob([$this, ($this->builtin && $method != 'single_row' ? 'builtin_' : '').$method], ...$args);
     127
     128            return $this->builtin && in_array($method, ['single_row', 'display']) ? $this->filter_table($result) : $result;
    108129        }elseif(try_remove_prefix($method, 'builtin_')){
    109130            return [$GLOBALS['wp_list_table'] ??= _get_list_table($this->builtin, ['screen'=>$this->screen]), $method](...$args);
    110131        }elseif(try_remove_prefix($method, 'filter_')){
    111132            if($method == 'table'){
    112                 return wpjam_preg_replace('#<tr id="'.$this->singular.'-(\d+)"[^>]*>(.+?)</tr>#is', fn($m)=> $this->filter_single_row($m[0], $m[1]), $args[0]);
     133                return wpjam_preg_replace('#<tr id=".+?-(\d+)"[^>]*>.+?</tr>#is', fn($m)=> $this->filter_single_row(...$m), $args[0]);
    113134            }elseif($method == 'single_row'){
    114135                return wpjam_do_shortcode(apply_filters('wpjam_single_row', ...$args), [
     
    116137                    'row_action'    => fn($attr, $title)=> $this->get_row_action($args[1], (is_blank($title) ? [] : compact('title'))+$attr)."\n"
    117138                ]);
    118             }elseif($method == 'custom_column'){
    119                 return count($args) == 2 ? wpjam_echo($this->column_default([], ...$args)) : $this->column_default(...$args);
    120             }
    121 
    122             $value  = $this->$method ?: [];
    123 
    124             if($method == 'columns'){
    125                 return wpjam_except(($args ? wpjam_add_at($args[0], -1, $value) : $value), wpjam_admin('removed_columns[]'));
    126             }elseif($method == 'row_actions'){
    127                 $args[1]= $this->layout == 'calendar' ? $args[1] : ['id'=>$this->parse_id($args[1])];
    128                 $value  = $args[0]+$this->get_actions(array_diff($value, $this->next_actions ?: []), $args[1]);
    129                 $value  += $this->builtin ? wpjam_pull($value, ['delete', 'trash', 'spam', 'remove', 'view']) : [];
    130 
    131                 return wpjam_except($value+($this->primary_key == 'id' || $this->builtin ? ['id'=>'ID: '.$args[1]['id']] : []), wpjam_admin('removed_actions[]'));
    132             }
    133 
    134             return array_merge($args[0], $value);
     139            }elseif($method == 'columns'){
     140                return $this->columns   = wpjam_except(wpjam_add_at($args[0], $args[0] ? -1 : 0, $this->columns ?: []), wpjam_admin('removed_columns[]'));
     141            }
     142
     143            return array_merge($args[0], $this->$method ?: []);
    135144        }
    136145
     
    160169            $this->prepare_items();
    161170
    162             $result = $this->response($result)+['params'=>$this->params, 'setting'=>$this->get_setting(), 'views'=>$this->ob_get_views(), 'search_box'=>$this->get_search_box()];
    163 
    164             $result += $result['type'] == 'list' ? ['table'=>$this->get_table()] : ['tablenav'=>wpjam_fill(['top', 'bottom'], fn($which)=>$this->ob_get_display_tablenav($which))];
     171            $result = $this->response($result)+['params'=>$this->params, 'setting'=>$this->get_setting(), 'views'=>$this->ob_get_views()];
     172            $result += $result['type'] == 'list' ? ['table'=>$this->ob_get_display()] : ['tablenav'=>wpjam_fill(['top', 'bottom'], [$this, 'ob_get_display_tablenav'])];
    165173        }
    166174
     
    180188            if($this->layout == 'calendar'){
    181189                if(!empty($data['data'])){
    182                     $data['data']   = wpjam_map(($data['data']['dates'] ?? $data['data']), fn($v, $k)=> $this->ob_get_single_date($v, $k));
     190                    $data['data']   = wpjam_map($data['data'], [$this, 'ob_get_single_date']);
    183191                }
    184192            }elseif(!empty($data['bulk'])){
     
    226234        }elseif($type == 'column'){
    227235            if($this->layout == 'calendar'){
    228                 $start  = (int)get_option('start_of_week');
    229                 $locale = $GLOBALS['wp_locale'];
    230 
    231                 for($i=$start; $i<$start+7; $i++){
    232                     $this->add('columns', 'day'.($i%7), $locale->get_weekday_abbrev($locale->get_weekday($i%7)));
    233                 }
    234 
    235                 return wpjam_map(['year', 'month'], fn($v)=> $this->add('query_args', $v));
     236                array_map(fn($i)=> $this->add('columns', 'day'.($i%7), $GLOBALS['wp_locale']->get_weekday_abbrev($GLOBALS['wp_locale']->get_weekday($i%7))), array_slice(range(0, get_option('start_of_week')+6), -7));
     237
     238                return array_map(fn($v)=> $this->add('query_args', $v), ['year', 'month']);
    236239            }
    237240
    238241            $this->bulk_actions && !$this->builtin && $this->add('columns', 'cb', true);
    239242
    240             $no = $this->numberable;
    241             $no && $this->add('columns', 'no', $no === true ? 'No.' : $no) && wpjam_admin('style', '.column-no{width:42px;}');
     243            ($no = $this->numberable) && $this->add('columns', 'no', $no === true ? 'No.' : $no);
    242244        }
    243245
     
    276278
    277279    protected function get_setting(){
    278         $s  = $this->get_data('s');
     280        $s      = $this->get_data('s');
     281        $fields = $this->searchable_fields;
    279282
    280283        return wpjam_pick($this, ['sortable', 'layout', 'left_key'])+[
     284            'search'    => $this->builtin || ($this->search ?? $fields) ? [
     285                'columns'   => wpjam_is_assoc_array($fields) ? wpjam_field(['key'=>'search_columns', 'value'=>$this->get_data('search_columns'), 'show_option_all'=>'默认', 'options'=>$fields]) : ''
     286            ]+($this->builtin ? ['term'=>$s] : ['box'=>$this->ob_get_search_box('搜索', 'wpjam')]) : false,
     287
    281288            'subtitle'  => $this->get_subtitle_by_model().($s ? sprintf(__('Search results for: %s', 'wpjam'), '<strong>'.esc_html($s).'</strong>') : ''),
    282289            'summary'   => $this->get_summary_by_model(),
    283290
    284             'column_count'      => $this->get_column_count(),
    285             'bulk_actions'      => wpjam_map($this->bulk_actions ?: [], fn($object)=> array_filter($object->get_data_attr(['bulk'=>true]))),
    286             'overall_actions'   => array_values($this->get_actions(array_diff($this->overall_actions ?: [], $this->next_actions ?: []), ['class'=>'button overall-action']))
     291            'column_count'      => [$this, ($this->builtin ? 'builtin_' : '').'get_column_count'](),
     292            'bulk_actions'      => array_map(fn($v)=> array_filter($v->get_data_attr(['bulk'=>true])), $this->bulk_actions ?: []),
     293            'overall_actions'   => array_values($this->get_action(array_diff($this->overall_actions ?: [], $this->next_actions ?: []), ['class'=>'button overall-action']))
    287294        ];
    288295    }
    289296
    290     protected function get_actions($names, $args=[]){
    291         return wpjam_fill($names ?: [], fn($k)=> $this->get_action($k, $args));
    292     }
    293 
    294297    public function get_action($name, ...$args){
     298        if(is_array($name)){
     299            return wpjam_fill($name ?: [], fn($n)=> $this->get_action($n, ...$args));
     300        }
     301
    295302        return ($object = $this->component('action', $name)) && $args ? $object->render($args[0]) : $object;
    296303    }
    297304
    298     public function get_row_action($id, $args=[]){
    299         return $this->get_action(...(isset($args['name']) ? [wpjam_pull($args, 'name'), $args+['id'=>$id]] : [$id, $args]));
     305    public function get_row_action($id, $args){
     306        return $this->get_action(wpjam_pull($args, 'name'), $args+['id'=>$id]);
    300307    }
    301308
     
    321328        $attr   = $id ? ['id'=>$this->singular.'-'.str_replace('.', '-', $id), 'data'=>['id'=>$id]] : [];
    322329
    323         $item['row_actions']    = $id ? $this->filter_row_actions([], $item) : ($this->row_actions ? ['error'=>'Primary Key「'.$this->primary_key.'」不存在'] : []);
     330        $item['row_actions']    = $id ? $this->get_row_actions($id) : ($this->row_actions ? ['error'=>'Primary Key「'.$this->primary_key.'」不存在'] : []);
    324331
    325332        $this->before_single_row_by_model($raw);
     
    338345    public function single_date($item, $date){
    339346        $parts  = explode('-', $date);
    340         $append = ($item || $parts[1] == $this->get_date()['month']) ? wpjam_tag('div', ['row-actions', 'alignright'])->append($this->filter_row_actions([], ['id'=>$date, 'wrap'=>'<span class="%s"></span>'])) : '';
     347        $append = ($item || $parts[1] == $this->get_date()['month']) ? wpjam_tag('div', ['row-actions', 'alignright'])->append($this->get_row_actions($date)) : '';
    341348
    342349        echo wpjam_tag('div', ['date-meta'])->append('span', ['day', $date == wpjam_date('Y-m-d') ? 'today' : ''], (int)$parts[2])->append($append)->after('div', ['date-content'], $this->render_date_by_model($item, $date) ?? (is_string($item) ? $item : ''));
     
    348355
    349356    protected function parse_cell($cell, $id){
    350         if(!is_array($cell)){
    351             return $cell;
    352         }
    353 
    354357        $wrap   = wpjam_pull($cell, 'wrap');
    355358
     
    376379                if($type == 'image'){
    377380                    $ar = wpjam_pick($data, ['width', 'height']);
    378                     $v  = wpjam_tag('img', ['src'=>wpjam_get_thumbnail($v, wpjam_map($ar, fn($s)=> $s*2))]+$ar)->after('span', ['item-title'], $item['title'] ?? '');
     381                    $v  = wpjam_tag('img', ['src'=>wpjam_get_thumbnail($v, array_map(fn($s)=> $s*2), $ar)]+$ar)->after('span', ['item-title'], $item['title'] ?? '');
    379382                }
    380383
     
    383386                $cell->append(wpjam_tag('div', ['id'=>'item_'.$i, 'data'=>['i'=>$i], 'class'=>'item'])->append([
    384387                    $this->get_action('move_item', $args+['title'=>$v, 'fallback'=>true])->style(wpjam_pick($item, ['color'])),
    385                     wpjam_tag('span', ['row-actions'])->append($this->get_actions(array_diff($names, ['add_item']), $args+['wrap'=>'<span class="%s"></span>', 'item'=>$item]))
     388                    wpjam_tag('span', ['row-actions'])->append($this->get_action(array_diff($names, ['add_item']), $args+['wrap'=>'<span class="%s"></span>', 'item'=>$item]))
    386389                ]));
    387390            }
     
    403406        $value  = $object && $id ? $object($args+['data'=>$item, 'id'=>$id]) : (is_array($item) ? ($item[$name] ?? null) : $item);
    404407
    405         return wp_is_numeric_array($value) ? implode(',', array_map(fn($v)=> $this->parse_cell($v, $id), $value)) : $this->parse_cell($value, $id);
     408        return implode(',', array_map(fn($v)=> is_array($v) ? $this->parse_cell($v, $id) : $v, wp_is_numeric_array($value) ? $value : [$value]));
    406409    }
    407410
    408411    public function column_cb($item){
    409412        if(($id = $this->parse_id($item)) && wpjam_can($this->capability, $id)){
    410             return wpjam_tag('input', ['type'=>'checkbox', 'name'=>'ids[]', 'value'=>$id, 'id'=>'cb-select-'.$id, 'title'=>'选择'.strip_tags($item[$this->get_primary_column_name()] ?? $id)]);
     413            return wpjam_tag('input', ['type'=>'checkbox', 'name'=>'ids[]', 'value'=>$id, 'id'=>'cb-select-'.$id, 'title'=>'选择'.strip_tags($item[$this->primary_column] ?? $id)]);
    411414        }
    412415    }
    413416
    414417    public function render(){
    415         $form   = wpjam_tag('form', ['id'=>'list_table_form'])->append([$this->get_search_box(), $this->get_table()])->before($this->ob_get_views());
    416 
    417         return $this->layout == 'left' ? wpjam_tag('div', ['id'=>'col-container', 'class'=>'wp-clearfix'])->append(wpjam_map([
    418             'left'  => wpjam_wrap($this->ob_get_col_left(), 'form'),
    419             'right' => $form
    420         ], fn($v, $k)=> $v->add_class('col-wrap')->wrap('div', ['id'=>'col-'.$k]))) : $form;
    421     }
    422 
    423     public function get_search_box(){
    424         $fields = $this->searchable_fields;
    425 
    426         if($this->search ?? $fields){
    427             $box    = $this->ob_get_search_box('搜索', 'wpjam') ?: wpjam_tag('p', ['search-box']);
    428             $field  = wpjam_is_assoc_array($fields) ? wpjam_field(['key'=>'search_columns', 'value'=>$this->get_data('search_columns'), 'type'=>'select', 'show_option_all'=>'默认', 'options'=>$fields]) : '';
    429        
    430             return $field ? wpjam_preg_replace('/(<p class="search-box">)/is', '$1'.$field, $box) : $box;
    431         }
    432     }
    433 
    434     public function get_table(){
    435         return $this->ob_get_display();
     418        $form   = wpjam_tag('form', ['id'=>'list_table_form'], $this->ob_get_display())->before($this->ob_get_views());
     419
     420        return $this->layout == 'left' ? wpjam_tag('div', ['id'=>'col-container', 'class'=>'wp-clearfix'])->append(array_map(fn($v, $k)=> $v->add_class('col-wrap')->wrap('div', ['id'=>'col-'.$k]), [wpjam_wrap($this->ob_get_col_left(), 'form'), $form], ['left', 'right'])) : $form;
    436421    }
    437422
     
    439424        $paged  = (int)$this->get_data('left_paged') ?: 1;
    440425
    441         if($cb = wpjam_callback($this->model.'::query_left')){
     426        if(($cb = $this->model.'::query_left') && wpjam_callback($cb)){
    442427            static $pages, $items;
    443428
     
    458443                ['p', ['row-title'], $item['title']],
    459444                ['span', ['time'], $item['time']],
    460                 ...(isset($item['count']) ? wpjam_map((array)$item['count'], fn($v)=> ['span', ['count', 'wp-ui-highlight'], $v]) : [])
     445                ...(isset($item['count']) ? array_map(fn($v)=> ['span', ['count', 'wp-ui-highlight'], $v], (array)$item['count']) : [])
    461446            ] : $item)->wrap('tr', is_array($item) ? ['class'=>'left-item', 'id'=>$item['id'], 'data-id'=>$item['id']] : ['no-items'])));
    462447
     
    478463    public function page_load(){
    479464        if(wp_doing_ajax()){
    480             return wpjam_ajax('wpjam-list-table-action',    [
     465            return wpjam_ajax('wpjam-list-table-action', [
    481466                'admin'         => true,
    482467                'callback'      => $this,
     
    485470        }
    486471
    487         if($action  = wpjam_get_parameter('export_action')){
     472        if($action = wpjam_get_parameter('export_action')){
    488473            return ($object = $this->get_action($action)) ? wpjam_trap($object, 'export', 'die') : wp_die('无效的导出操作');
    489474        }
    490475
    491         wpjam_trap([$this, 'prepare_items'], fn($result)=> wpjam_admin('error', $result));
     476        $this->builtin || wpjam_trap([$this, 'prepare_items'], fn($result)=> wpjam_admin('error', $result));
    492477    }
    493478
     
    531516    }
    532517
    533     protected function get_default_primary_column_name(){
    534         return $this->primary_column ?: array_find_key($this->get_columns(), fn($v, $k)=> !in_array($k, ['no', 'cb']));
    535     }
    536 
    537518    protected function handle_row_actions($item, $column, $primary){
    538519        return ($primary === $column && !empty($item['row_actions'])) ? $this->row_actions($item['row_actions']) : '';
     
    540521
    541522    public function get_columns(){
    542         return $this->filter_columns();
     523        return [];
    543524    }
    544525
     
    546527        if($this->layout == 'calendar'){
    547528            echo wpjam_tag('h2', [], $this->get_date('locale'));
    548             echo wpjam_tag('span', ['pagination-links'])->append(wpjam_map(['prev'=>'&lsaquo;', 'current'=>'今日', 'next'=>'&rsaquo;'], fn($v, $k)=> "\n".$this->get_filter_link($this->get_date($k), $v, ['class'=>$k.'-month button', 'title'=>$this->get_date($k, 'locale')])))->wrap('div', ['tablenav-pages']);
    549         }
    550 
    551         if($which == 'top' && ($fields = (wpjam_admin('chart', 'get_fields') ?: [])+array_filter($this->filterable_fields))){
     529            echo wpjam_tag('span', ['pagination-links'])->append(array_map(fn($v, $k)=> "\n".$this->get_filter_link($this->get_date($k), $v, ['class'=>$k.'-month button', 'title'=>$this->get_date($k, 'locale')]), ['&lsaquo;', '今日', '&rsaquo;'], ['prev', 'current', 'next']))->wrap('div', ['tablenav-pages']);
     530        }
     531
     532        if($which == 'top' && ($fields = (wpjam_chart('get_fields') ?: [])+array_filter($this->filterable_fields))){
    552533            echo wpjam_fields($fields, ['fields_type'=>'', 'data'=>$this->params])->wrap('div', ['actions'])->append(get_submit_button('筛选', '', 'filter_action', false));
    553534        }
     
    854835        $fields = wpjam_try('maybe_callback', $this->fields, $arg, $this->name) ?: wpjam_try($this->model.'::get_fields', $this->name, $arg);
    855836        $fields = array_merge(is_array($fields) ? $fields : [], ($prev ? $this->get_prev_fields($arg, true, '') : []));
    856         $fields = ($cb = wpjam_callback($this->model.'::filter_fields')) ? wpjam_try($cb, $fields, $arg, $this->name) : $fields;
     837        $fields = ($cb = $this->model.'::filter_fields') && wpjam_callback($cb) ? wpjam_try($cb, $fields, $arg, $this->name) : $fields;
    857838
    858839        if(!in_array($this->name, ['add', 'duplicate']) && isset($fields[$this->primary_key])){
     
    957938    public function __invoke($args){
    958939        $id     = $args['id'];
    959         $value  = $this->_field->val(null)->value_callback($args) ?? wpjam_value_callback($args, $this->name) ?? $this->default;
     940        $value  = $this->_field->val(null)->value_callback($args) ?? wpjam_value($args, $this->name) ?? $this->default;
    960941
    961942        if(wpjam_is_assoc_array($value)){
     
    10481029class WPJAM_Builtin_List_Table extends WPJAM_List_Table{
    10491030    public function __construct($args){
    1050         $screen     = get_current_screen();
    1051         $data_type  = wpjam_admin('data_type', $args['data_type']);
     1031        $data_type  = wpjam_admin(wpjam_pick($args, ['data_type', 'meta_type']))['data_type'];
    10521032
    10531033        if($data_type == 'post_type'){
    1054             $parts  = $screen->id == 'upload' ? ['media', 'media'] : ($args['hierarchical'] ? ['pages', 'page', 'posts'] : ['posts', 'post', 'posts']);
    1055             $args   += ['builtin'=> $parts[0] == 'media' ? 'WP_Media_List_Table' : 'WP_Posts_List_Table'];
     1034            $echo       = 'echo';
     1035            $builtin    = $args['post_type'] == 'attachment' ? 'Media' : 'Posts';
     1036            $callback   = 'get_post';
     1037            $parts      = $builtin == 'Media' ? ['media', 'media'] : ($args['hierarchical'] ? ['pages', 'page', 'posts'] : ['posts', 'post', 'posts']);
    10561038        }elseif($data_type == 'taxonomy'){
    1057             $args   += ['builtin'=>'WP_Terms_List_Table'];
    1058             $parts  = [$args['taxonomy'], $args['taxonomy']];
     1039            $builtin    = 'Terms';
     1040            $callback   = ['get_term', 'get_term_level'];
     1041            $parts      = [$args['taxonomy'], $args['taxonomy']];
    10591042        }elseif($data_type == 'user'){
    1060             $args   += ['builtin'=>'WP_Users_List_Table'];
    1061             $parts  = ['users', 'user', 'users'];
     1043            $builtin    = 'Users';
     1044            $callback   = 'get_userdata';
     1045            $parts      = ['users', 'user', 'users'];
    10621046        }elseif($data_type == 'comment'){
    1063             $args   += ['builtin'=>'WP_Comments_List_Table'];
    1064             $parts  = ['comments', 'comment'];
    1065         }
    1066 
    1067         wpjam_admin('meta_type', $args['meta_type'] ?? '');
    1068 
    1069         add_filter('manage_'.$screen->id.'_columns',    [$this, 'filter_columns']);
    1070         add_filter('manage_'.$parts[0].'_custom_column',[$this, 'filter_custom_column'], 10, in_array($data_type, ['post_type', 'comment']) ? 2 : 3);
    1071 
    1072         add_filter($parts[1].'_row_actions',    [$this, 'filter_row_actions'], 1, 2);
     1047            $echo       = 'echo';
     1048            $builtin    = 'Comments';
     1049            $callback   = 'get_comment';
     1050            $parts      = ['comments', 'comment'];
     1051        }
     1052
     1053        wpjam_hook(($echo ?? ''), 'manage_'.$parts[0].'_custom_column', fn(...$args)=> $this->column_default(...array_pad($args, -3, [])), 10, 3);
     1054
     1055        add_filter($parts[1].'_row_actions', [$this, 'filter_row_actions'], 1, 2);
    10731056
    10741057        isset($parts[2]) && add_action('manage_'.$parts[2].'_extra_tablenav', [$this, 'extra_tablenav']);
    1075         in_array($data_type, ['post_type', 'taxonomy']) && add_action('parse_term_query', [$this, 'on_parse_query'], 0);
     1058
    10761059        wp_is_json_request() || add_filter('wpjam_html', [$this, 'filter_table']);
    10771060
    1078         parent::__construct($args);
    1079     }
    1080 
    1081     public function views(){
    1082         $this->screen->id != 'upload' && $this->builtin_views();
    1083     }
    1084 
    1085     public function display_tablenav($which){
    1086         $this->builtin_display_tablenav($which);
    1087     }
    1088 
    1089     public function get_table(){
    1090         return $this->filter_table($this->ob_get_builtin_display());
     1061        in_array($data_type, ['post_type', 'taxonomy']) && add_action('parse_term_query', function($query){
     1062            if(array_any(debug_backtrace(), fn($v)=> wpjam_get($v, 'class') == $this->builtin)){
     1063                $vars   = &$query->query_vars;
     1064                $by     = $vars['orderby'] ?? '';
     1065                $object = ($by && is_string($by)) ? $this->component('column', $by) : null;
     1066                $type   = $object ? ($object->sortable === true ? 'meta_value' : $object->sortable) : '';
     1067                $vars   = array_merge($vars, ['list_table_query'=>true], in_array($type, ['meta_value_num', 'meta_value']) ? ['orderby'=>$type, 'meta_key'=>$by] : []);
     1068            }
     1069        }, 0);
     1070
     1071        parent::__construct($args+['builtin'=>'WP_'.$builtin.'_List_Table', 'item_callback'=>$callback]);
    10911072    }
    10921073
    10931074    public function prepare_items(){
    1094         if(wp_doing_ajax()){
    1095             if($this->screen->base == 'edit'){
    1096                 $_GET['post_type']  = $this->post_type;
    1097             }
    1098 
    1099             $_GET   = array_merge($_GET, $this->get_data());
    1100             $_POST  = array_merge($_POST, $this->get_data());
    1101 
    1102             $this->builtin_prepare_items();
    1103         }
     1075        if($this->screen->base == 'edit'){
     1076            $_GET['post_type']  = $this->post_type;
     1077        }
     1078
     1079        $_GET   = array_merge($_GET, $this->get_data());
     1080        $_POST  = array_merge($_POST, $this->get_data());
     1081
     1082        $this->builtin_prepare_items();
    11041083    }
    11051084
    11061085    public function single_row($item){
    1107         if($this->data_type == 'post_type'){
    1108             global $post, $authordata;
    1109 
    1110             $post   = is_numeric($item) ? get_post($item) : $item;
    1111 
    1112             if($post){
    1113                 $authordata = get_userdata($post->post_author);
    1114 
    1115                 if($post->post_type == 'attachment'){
    1116                     echo wpjam_tag('tr', ['id'=>'post-'.$post->ID], $this->ob_get_builtin_single_row_columns($post))->add_class(['author-'.((get_current_user_id() == $post->post_author) ? 'self' : 'other'), 'status-'.$post->post_status]);
    1117                 }else{
    1118                     $args   = [$post];
    1119                 }
    1120             }
    1121         }elseif($this->data_type == 'taxonomy'){
    1122             $term   = is_numeric($item) ? get_term($item) : $item;
    1123             $args   = $term ? [$term, get_term_level($term)] : [];
    1124         }elseif($this->data_type == 'user'){
    1125             $user   = is_numeric($item) ? get_userdata($item) : $item;
    1126             $args   = $user ? [$user] : [];
    1127         }elseif($this->data_type == 'comment'){
    1128             $comment    = is_numeric($item) ? get_comment($item) : $item;
    1129             $args       = $comment ? [$comment] : [];
    1130         }
    1131 
    1132         echo empty($args) ? '' : $this->filter_table($this->ob_get_builtin_single_row(...$args));
    1133     }
    1134 
    1135     public function on_parse_query($query){
    1136         if(array_any(debug_backtrace(), fn($v)=> wpjam_get($v, 'class') == $this->builtin)){
    1137             $vars   = &$query->query_vars;
    1138             $by     = $vars['orderby'] ?? '';
    1139             $object = ($by && is_string($by)) ? $this->component('column', $by) : null;
    1140             $type   = $object ? ($object->sortable === true ? 'meta_value' : $object->sortable) : '';
    1141             $vars   = array_merge($vars, ['list_table_query'=>true], in_array($type, ['meta_value_num', 'meta_value']) ? ['orderby'=>$type, 'meta_key'=>$by] : []);
     1086        $cb = (array)$this->item_callback;
     1087
     1088        if($item = is_numeric($item) ? (array_shift($cb))($item) : $item){
     1089            if($this->data_type == 'post_type' && $item->post_type == 'attachment'){
     1090                $GLOBALS['authordata'] = get_userdata($item->post_author);
     1091
     1092                echo wpjam_tag('tr', ['id'=>'post-'.$item->ID], $this->ob_get_single_row_columns($item))->add_class(['author-'.(get_current_user_id() == $item->post_author ? 'self' : 'other'), 'status-'.$item->post_status]);
     1093            }else{
     1094                $this->builtin_single_row($item, ...array_map(fn($v)=> $v($item), $cb));
     1095            }
    11421096        }
    11431097    }
  • wpjam-basic/trunk/includes/class-wpjam-model.php

    r3427374 r3454739  
    337337            }
    338338
    339             $vars   += ['post_status'=>'publish', 'post__not_in'=>[$post->ID], 'post_type'=>array_unique($type), 'term_taxonomy_ids'=>wpjam_filter($tt_ids, 'unique')];
     339            $vars   += ['post_status'=>'publish', 'post__not_in'=>[$post->ID], 'post_type'=>array_unique($type), 'orderby'=>'related', 'term_taxonomy_ids'=>wpjam_filter($tt_ids, 'unique')];
    340340        }
    341341
  • wpjam-basic/trunk/includes/class-wpjam-post.php

    r3423058 r3454739  
    586586        add_filter('posts_clauses', function($clauses, $query){
    587587            $wpdb       = $GLOBALS['wpdb'];
    588             $orderby    = $query->get('related_query') ? 'related' : $query->get('orderby');
     588            $orderby    = $query->get('orderby');
    589589            $order      = $query->get('order') ?: 'DESC';
    590590
    591591            if($orderby == 'related'){
    592                 if($tt_ids  = $query->get('term_taxonomy_ids')){
     592                if($tt_ids = $query->get('term_taxonomy_ids')){
    593593                    $clauses['join']    .= "INNER JOIN {$wpdb->term_relationships} AS tr ON {$wpdb->posts}.ID = tr.object_id";
    594594                    $clauses['where']   .= " AND tr.term_taxonomy_id IN (".implode(",", $tt_ids).")";
  • wpjam-basic/trunk/includes/class-wpjam-user.php

    r3440469 r3454739  
    665665            wp_enqueue_script('wpjam-login', wpjam_url(dirname(__DIR__).'/static/login.js'), ['wpjam-ajax']);
    666666
    667             add_action('login_form', fn()=> wpjam_echo(wpjam_tag('p')->add_class('types')->data('action', $action)->append($append)));
     667            wpjam_hook('echo', 'login_form', fn()=> wpjam_tag('p')->add_class('types')->data('action', $action)->append($append));
    668668        }
    669669
  • wpjam-basic/trunk/public/wpjam-compat.php

    r3440469 r3454739  
    289289}
    290290
     291function wpjam_sum($items, $keys){
     292    return array_reduce($items, fn($sum, $item)=> array_map(fn($k)=> $sum[$k]+(is_numeric($v = str_replace(',', '', ($item[$k] ?? 0))) ? $v : 0), $keys), array_fill_keys($keys, 0));
     293}
     294
    291295function wpjam_migrate_option($from, $to, $default=null){
    292296    if(get_option($to, $default) === $default){
     
    577581
    578582function wpjam_get_list_table_row_action($name, $args=[]){
    579     return $GLOBALS['wpjam_list_table']->get_row_action($name, $args);
     583    return $GLOBALS['wpjam_list_table']->get_action($name, $args);
    580584}
    581585
     
    898902}
    899903
     904function wpjam_line_chart($data, $labels, $args=[]){
     905    echo wpjam_chart('line', $data, ['labels'=>$labels]+$args);
     906}
     907
     908function wpjam_bar_chart($data, $labels, $args=[]){
     909    echo wpjam_chart('bar', $data, ['labels'=>$labels]+$args);
     910}
     911
     912function wpjam_donut_chart($data, ...$args){
     913    echo wpjam_chart('donut', $data, (count($args) >= 2 ? ['labels'=>array_shift($args)] : [])+($args[0] ?? []));
     914}
     915
     916function wpjam_get_chart_parameter(...$args){
     917    return wpjam_chart('get_parameter', ...$args);
     918}
     919
    900920function wpjam_stats_header($args=[]){
    901921    global $wpjam_stats_labels;
     
    10081028}
    10091029
    1010 function_alias('wpjam_add_once_filter', 'wpjam_add_once_action');
    1011 
    1012 function_alias('wpjam_hook', 'wpjam_add_filter');
    1013 function_alias('wpjam_hook', 'wpjam_add_action');
    1014 
    1015 function_alias('wpjam_option', 'wpjam_setting');
     1030function_alias('wpjam_every',   'array_all');
     1031function_alias('wpjam_some',    'array_any');
     1032function_alias('wpjam_array',   'array_wrap');
     1033function_alias('wpjam_get',     'array_get');
     1034function_alias('wpjam_set',     'array_set');
     1035function_alias('wpjam_merge',   'merge_deep');
     1036
     1037function_alias('wpjam_hook',    'wpjam_add_filter');
     1038
     1039function_alias('wpjam_option',  'wpjam_setting');
    10161040
    10171041function_alias('wpjam_ob', 'wpjam_ob_get_contents');
  • wpjam-basic/trunk/public/wpjam-functions.php

    r3440469 r3454739  
    174174
    175175function wpjam_is_json_request(){
    176     return get_option('permalink_structure') ? (bool)preg_match("/\/api\/.*\.json/", $_SERVER['REQUEST_URI']) : wpjam_get_parameter('module') == 'json';
     176    return get_option('permalink_structure') ? (bool)preg_match("/\/api\/.*\.json/", $_SERVER['REQUEST_URI']) : (($_GET['module'] ?? '') == 'json');
    177177}
    178178
     
    478478}
    479479
    480 function wpjam_pagenavi($total=0, $echo=true){
     480function wpjam_pagenavi($total=0, $display=true){
    481481    $result = '<div class="pagenavi">'.paginate_links(array_filter(['prev_text'=>'&laquo;', 'next_text'=>'&raquo;', 'total'=>$total])).'</div>';
    482482
    483     return $echo ? wpjam_echo($result) : $result;
     483    return $display ? wpjam_echo($result) : $result;
    484484}
    485485
     
    675675        return ($comment = get_comment($comment_id)) ? $comment->comment_parent : null;
    676676    }
     677}
     678
     679// Shortcode
     680function wpjam_do_shortcode($content, $tags, $ignore_html=false){
     681    if(wpjam_is_assoc_array($tags)){
     682        $tags   = array_keys(wpjam_map($tags, fn($cb, $tag)=> shortcode_exists($tag) || add_shortcode($tag, $cb)));
     683    }
     684
     685    if($tags && array_any($tags, fn($tag)=> str_contains($content, '['.$tag))){
     686        $content    = do_shortcodes_in_html_tags($content, $ignore_html, $tags);
     687        $content    = preg_replace_callback('/'.get_shortcode_regex($tags).'/', 'do_shortcode_tag', $content);
     688        $content    = unescape_invalid_shortcodes($content);
     689    }
     690
     691    return $content;
     692}
     693
     694function wpjam_parse_shortcode_attr($str, $tag){
     695    return preg_match('/'.get_shortcode_regex((array)$tag).'/', $str, $m) ? shortcode_parse_atts($m[3]) : [];
    677696}
    678697
     
    795814        $upload = wp_upload_bits($name, null, $bits);
    796815    }else{
    797         $args   += ['test_form'=>false];
     816        wpjam_validate_mime_types($args['mimes'] ?? []) && ($args += ['test_form'=>false]);
     817
    798818        $upload = is_array($name) ? wp_handle_sideload($name, $args) : wp_handle_upload($_FILES[$name], $args);
    799819    }
     
    966986}
    967987
     988function wpjam_validate_mime_types($mimes){
     989    $allowed    = get_allowed_mime_types();
     990
     991    foreach($mimes as $k => $v){
     992        $ext    = array_search($v, $allowed);
     993
     994        if(!$ext || array_diff(explode('|', $k), explode('|', $ext))){
     995            wpjam_throw('upload_error', '无效的文件类型');
     996        }
     997    }
     998
     999    return true;
     1000}
     1001
    9681002function wpjam_accept_to_mime_types($accept){
    9691003    $allowed    = get_allowed_mime_types();
     
    10161050// Field
    10171051function wpjam_fields($fields, ...$args){
    1018     if(is_object($fields)){
    1019         $object = $fields;
    1020         $fields = [];
    1021 
    1022         foreach($object->get_arg('_fields[]') as $key => $field){
    1023             if(is_callable($field)){
    1024                 $result = wpjam_try($field, ...$args);
    1025 
    1026                 if(is_numeric($key)){
    1027                     $fields = array_merge($fields, $result);
    1028                 }else{
    1029                     $fields[$key]   = $result;
    1030                 }
    1031             }elseif(wpjam_is_assoc_array($field)){
    1032                 $fields[$key]   = $field;
    1033             }
    1034         }
    1035 
    1036         return $fields;
    1037     }
    1038 
    1039     if($args && is_bool($args[0])){
    1040         return WPJAM_Fields::parse($fields, $args[0]);
     1052    if(is_object($fields) || ($args && is_bool($args[0]))){
     1053        return WPJAM_Fields::parse($fields, ...$args);
    10411054    }
    10421055
     
    10451058    $object = WPJAM_Fields::create($fields, $args);
    10461059
    1047     if($echo){
    1048         echo $object; // del 2026-06-30
    1049     }
    1050 
    1051     return $object;
     1060    return $echo ? wpjam_echo($object) : $object;   
    10521061}
    10531062
    10541063function wpjam_field($field, ...$args){
    10551064    if(is_object($field)){
    1056         $object = $field;
    1057         $key    = array_shift($args);
    1058 
    1059         if(is_array($key)){
    1060             return wpjam_reduce($key, fn($c, $v, $k)=> wpjam_field($object, $k, $v), $object);
    1061         }
    1062 
    1063         if(!$args && is_callable($key)){
    1064             [$args, $key]   = [[$key], ''];
    1065         }
    1066 
    1067         return [$object, $args ? 'update_arg' : 'delete_arg']('_fields['.$key.']', ...$args);
    1068     }elseif(is_array($field)){
     1065        return WPJAM_Field::parse($field, ...$args);
     1066    }
     1067
     1068    if(is_array($field)){
     1069        $args   = $args[0] ?? [];
     1070        $echo   = wpjam_pull($field, 'echo') ?? wpjam_pull($args, 'echo');
    10691071        $object = WPJAM_Field::create($field);
    1070         $args   = $args[0] ?? [];
    1071 
    1072         return $args ? (isset($args['wrap_tag']) ? $object->wrap(wpjam_pull($args, 'wrap_tag'), $args) : $object->render($args)) : $object;
     1072        $wrap   = wpjam_pull($args, 'wrap_tag');
     1073        $result = isset($wrap) ? $object->wrap($wrap, $args) : ($args ? $object->render($args) : $object);
     1074
     1075        return $echo ? wpjam_echo($result) : $result;
     1076    }
     1077}
     1078
     1079function wpjam_parse_show_if($if){
     1080    if(wp_is_numeric_array($if) && count($if) >= 2){
     1081        $keys   = count($if) == 2 ? ['key', 'value'] : ['key', 'compare', 'value'];
     1082
     1083        if(count($if) > 3){
     1084            if(is_array($if[3])){
     1085                $args   = $if[3];
     1086
     1087                trigger_error(var_export($args, true)); // del 2025-12-30
     1088            }
     1089
     1090            $if = array_slice($if, 0, 3);
     1091        }
     1092
     1093        return array_combine($keys, $if)+($args ?? []);
     1094    }elseif(is_array($if) && !empty($if['key'])){
     1095        return $if;
    10731096    }
    10741097}
     
    11431166        }
    11441167
    1145         $notice = wpjam_pull($data, 'notice');
    1146         $errmsg = wpjam_pull($data, 'errmsg');
    1147         $notice = !$notice && $errmsg && $errmsg != 'ok' ? $errmsg : '';    // 第三方接口可能返回 ok
    1148 
    1149         return ['notice'=>$notice];
    1150     }
     1168        return ['notice'=>wpjam_pull($data, 'notice') ?: (($errmsg = wpjam_pull($data, 'errmsg')) && $errmsg != 'ok' ? $errmsg : '')];  // 第三方接口可能返回 ok
     1169    }
     1170}
     1171
     1172// translate
     1173function wpjam_translate($text, ...$args){
     1174    if(($domain = wpjam_at($args, -1)) != 'default'){
     1175        $cb = 'translate'.(count($args) >= 2 ? '_with_gettext_context' : '');
     1176
     1177        if($domain === 'wpjam' && ($low = strtolower($text)) !== $text && count(explode(' ', $text)) <= 2 && ($trans = $cb($low, ...$args)) != $low){
     1178            return $trans;
     1179        }
     1180
     1181        return $cb($text, ...array_slice($args, 0, -1));
     1182    }
     1183
     1184    return $text;
    11511185}
    11521186
  • wpjam-basic/trunk/public/wpjam-route.php

    r3440469 r3454739  
    6363    }
    6464
    65     if($name && $args){
    66         if(wp_is_numeric_array($args[0]) && !is_callable($args[0])){
    67             return wpjam_map(array_shift($args), fn($cb)=> wpjam_hook($type, $name, $cb, ...$args));
    68         }
    69 
    70         return wpjam_hook($type, $name, ...$args);
     65    if($name && $args && ($cb = array_shift($args))){
     66        return wpjam_map(wp_is_numeric_array($cb) && !is_callable($cb) ? $cb : [$cb], fn($cb)=> wpjam_hook($type, $name, $cb, ...$args));
    7167    }
    7268}
     
    7773    }
    7874
    79     $once   = $name === 'once';
    80     $name   = ($once || !$name) ? array_shift($args) : $name;
     75    $attr   = in_array($name, ['once', 'echo', 'tap'], true) ? [$name => true] : [];
     76    $name   = ($attr || !$name) ? array_shift($args) : $name;
    8177    $cb     = array_shift($args);
    82     $assoc  = wpjam_is_assoc_array($cb);
    83 
    84     if($once || $assoc){
    85         $object = wpjam_args(($once ? ['once'=>$once] : [])+($assoc ? $cb : ['callback'=>$cb]));
     78
     79    if($attr += wpjam_is_assoc_array($cb) ? $cb : ($attr ? ['callback'=>$cb] : [])){
     80        $object = wpjam_args($attr);
    8681        $cb     = wpjam_bind(function(...$args){
    8782            if($this->check && !($this->check)(...$args)){
     
    8984            }
    9085
    91             if($this->once === true){
     86            if($this->once){
    9287                $hook   = $GLOBALS['wp_filter'][current_filter()];
    9388
     
    9590            }
    9691
    97             return ($this->callback)(...$args);
     92            $result = ($this->callback)(...$args);
     93
     94            if($this->echo){
     95                echo $result;
     96            }
     97
     98            return $this->tap ? $args[0] : $result;
    9899        }, $object);
    99100
     
    106107}
    107108
    108 function wpjam_bind($cb, $args){
    109     if(is_closure($cb)){
    110         $object = is_object($args) ? $args : wpjam_args($args);
    111 
    112         return $cb->bindTo($object, $object);
    113     }
    114 
    115     return $cb;
    116 }
    117 
    118109function wpjam_callback($cb, $parse=false, &$args=[]){
    119     if(is_string($cb) && ($sep  = array_find(['::', '->'], fn($v)=> str_contains($cb, $v)))){
     110    if(is_string($cb) && ($sep = array_find(['::', '->'], fn($v)=> str_contains($cb, $v)))){
    120111        $static = $sep == '::';
    121112        $cb     = explode($sep, $cb, 2);
     
    164155        return $cb(...$args);
    165156    }
     157}
     158
     159function wpjam_call_multiple($cb, $args){
     160    return array_map(fn($v)=> wpjam_call($cb, ...(array)$v), $args);
     161}
     162
     163function wpjam_bind($cb, $args){
     164    return is_closure($cb) ? $cb->bindTo(...array_fill(0, 2, is_object($args) ? $args : wpjam_args($args))) : $cb;
    166165}
    167166
     
    201200}
    202201
    203 function wpjam_value_callback($args, $name){
     202function wpjam_value($model, $name, ...$args){
     203    if(is_string($model)){
     204        return wpjam_call($model.'::get_'.$name, ...$args);
     205    }
     206
     207    $args   = $model;
    204208    $names  = (array)$name;
    205209    $key    = 'value_callback';
     
    214218        $model  = $args['model'] ?? '';
    215219
    216         if($id && count($names) >= 2 && $names[0] == 'meta_input' && ($meta_type = ($args['meta_type'] ?? '') ?: wpjam_call($model.'::get_meta_type'))){
     220        if($id && count($names) >= 2 && $names[0] == 'meta_input' && ($meta_type = ($args['meta_type'] ?? '') ?: wpjam_value($model, 'meta_type'))){
    217221            $args['meta_type']  = $meta_type;
    218222
     
    249253        return $cb();
    250254    }finally{
    251         array_map(fn($args)=> add_filter(...$args), $suppressed);
     255        wpjam_call_multiple('add_filter', $suppressed);
    252256    }
    253257}
     
    278282        $ref    = class_exists($cb[0]) ? wpjam_var('reflection:class['.strtolower($cb[0]).']', fn()=> new ReflectionClass($cb[0])) : null;
    279283    }else{
    280         $ref    = ($cb = wpjam_callback($cb)) ? wpjam_var('reflection:cb['.wpjam_build_callback_unique_id($cb).']', is_array($cb) ? new ReflectionMethod(...$cb) : new ReflectionFunction($cb)) : null;
     284        $ref    = ($cb = wpjam_callback($cb)) ? wpjam_var('reflection:cb['.wpjam_build_callback_unique_id($cb).']', fn()=> is_array($cb) ? new ReflectionMethod(...$cb) : new ReflectionFunction($cb)) : null;
    281285    }
    282286
     
    617621
    618622    if(is_array($name)){
    619         return $name ? wpjam_map((wp_is_numeric_array($name) ? array_fill_keys($name, $args) : $name), fn($v, $n)=> wpjam_get_parameter($n, $v)) : [];
     623        return $name ? wpjam_map(wp_is_numeric_array($name) ? array_fill_keys($name, $args) : $name, 'wpjam_get_parameter', 'kv') : [];
    620624    }
    621625
     
    783787function wpjam_route($module, $args, $query_var=false){
    784788    if(is_string($args) && class_exists($args)){
    785         foreach(['rewrite_rule', 'menu_page', 'admin_load'] as $k){
    786             if($k == 'rewrite_rule' || is_admin()){
    787                 $v  = wpjam_callback([$args, 'get_'.$k]);
    788                 $v && ('wpjam_add_'.$k)($k == 'rewrite_rule' ? $v : maybe_callback($v));
    789             }
    790         }
    791 
    792         $args   = ['callback'=>$args.'::redirect'];
     789        $model  = $args;
     790        $args   = ['callback'=>$model.'::redirect'];
     791
     792        wpjam_init(fn()=> ($rules = wpjam_value($model, 'rewrite_rule')) && is_array($rules) && wpjam_call_multiple('wpjam_add_rewrite_rule', is_array($rules[0]) ? $rules : [$rules]));
     793
     794        is_admin() && array_map(fn($k)=> wpjam_call('wpjam_add_'.$k, wpjam_value($model, $k)), ['menu_page', 'admin_load']);
    793795    }else{
    794796        $args   = wpjam_is_assoc_array($args) ? array_filter($args) : ['callback'=>$args];
     
    802804    if(!wpjam('route')){
    803805        add_filter('query_vars',    fn($vars)=> array_merge($vars, ['module', 'action', 'term_id']), 11);
    804         add_filter('request',       fn($vars)=> wpjam_parse_query_vars($vars), 11);
     806        add_filter('request',       'wpjam_parse_query_vars', 11);
    805807        add_action('parse_request', 'wpjam_dispatch', 1);
    806808    }
    807809
    808810    wpjam('route[]', $module, $args+['query_var'=>$query_var]);
     811}
     812
     813function wpjam_add_rewrite_rule(...$args){
     814    return add_rewrite_rule($GLOBALS['wp_rewrite']->root.array_shift($args), ...$args);
    809815}
    810816
    811817function wpjam_dispatch($module, $action=''){
    812818    if(is_object($module)){
    813         $wp     = $module;
    814         $module = $wp->query_vars['module'] ?? '';
    815         $action = $wp->query_vars['action'] ?? '';
     819        $vars   = $module->query_vars;
     820        $module = $vars['module'] ?? '';
     821        $action = $vars['action'] ?? '';
    816822
    817823        if(!$module){
     
    864870    $result = [wpjam_get_handler(['items_type'=>'transient', 'transient'=>'wpjam-actives']), ($args ? 'add' : 'empty')](...$args);
    865871
    866     return $args ? $result : wpjam_map($result, fn($active)=> $active && count($active) >= 2 && add_action(...$active));
     872    return $args ? $result : wpjam_call_multiple('add_action', $result);
    867873}
    868874
     
    10201026}
    10211027
    1022 // Rewrite Rule
    1023 function wpjam_add_rewrite_rule($args){
    1024     if(did_action('init')){
    1025         $args   = maybe_callback($args);
    1026 
    1027         if($args && is_array($args)){
    1028             if(is_array($args[0])){
    1029                 array_walk($args, 'wpjam_add_rewrite_rule');
    1030             }else{
    1031                 add_rewrite_rule(...[$GLOBALS['wp_rewrite']->root.array_shift($args), ...$args]);
    1032             }
    1033         }
    1034     }else{
    1035         wpjam_init(fn()=> wpjam_add_rewrite_rule($args));
    1036     }
    1037 }
    1038 
    10391028// Menu Page
    10401029function wpjam_add_menu_page(...$args){
     1030    if(!$args[0]){
     1031        return;
     1032    }
     1033
     1034    if(wp_is_numeric_array($args[0])){
     1035        return array_walk($args[0], 'wpjam_add_menu_page');
     1036    }
     1037
    10411038    if(is_array($args[0])){
    10421039        $args   = $args[0];
    1043 
    1044         if(wp_is_numeric_array($args)){
    1045             return array_walk($args, 'wpjam_add_menu_page');
    1046         }
    10471040    }else{
    10481041        $key    = empty($args[1]['plugin_page']) ? 'menu_slug' : 'tab_slug';
     
    11041097        }
    11051098
     1099        if(is_array($key)){
     1100            return wpjam_map($key, 'wpjam_admin', 'kv');
     1101        }
     1102
    11061103        if(method_exists($object, $key)){
    11071104            return $object->$key(...$args);
     
    11141111        }
    11151112
    1116         if(is_object($value)){
     1113        if(is_object($value) && !is_object($args[0])){
    11171114            return count($args) >= 2 ? ($value->{$args[0]} = $args[1]) : $value->{$args[0]};
    11181115        }
     
    11341131    }
    11351132
     1133    function wpjam_chart($type='', ...$args){
     1134        if(in_array($type, ['line', 'bar', 'donut'], true)){
     1135            return ['WPJAM_Chart', $type](...$args);
     1136        }
     1137
     1138        $object = wpjam_admin('chart', ...(is_array($type) ? [WPJAM_Chart::create($type)] : []));
     1139
     1140        return $object && $type && !is_array($type) ? $object->$type(...$args) : $object;
     1141    }
     1142
    11361143    function wpjam_add_admin_load($args){
    1137         if(wp_is_numeric_array($args)){
    1138             array_walk($args, 'wpjam_add_admin_load');
    1139         }else{
    1140             $type   = wpjam_pull($args, 'type') ?: array_find(['base'=>'builtin_page', 'plugin_page'=>'plugin_page'], fn($v, $k)=> isset($args[$k])) ?: '';
    1141 
    1142             in_array($type, ['builtin_page', 'plugin_page']) && wpjam_admin($type.'_load[]', $args);
    1143         }
     1144        wp_is_numeric_array($args) ? array_walk($args, 'wpjam_add_admin_load') : $args && wpjam_admin('load', wpjam_pull($args, 'type'), $args);
    11441145    }
    11451146
     
    11801181    }
    11811182
    1182     function wpjam_chart($type, $data, $args){
    1183     }
    1184 
    1185     function wpjam_line_chart($data, $labels, $args=[]){
    1186         echo WPJAM_Chart::line(array_merge($args, ['labels'=>$labels, 'data'=>$data]));
    1187     }
    1188 
    1189     function wpjam_bar_chart($data, $labels, $args=[]){
    1190         echo WPJAM_Chart::line(array_merge($args, ['labels'=>$labels, 'data'=>$data]), 'Bar');
    1191     }
    1192 
    1193     function wpjam_donut_chart($data, ...$args){
    1194         $args   = count($args) >= 2 ? array_merge($args[1], ['labels'=> $args[0]]) : ($args[0] ?? []);
    1195 
    1196         echo WPJAM_Chart::donut(array_merge($args, ['data'=>$data]));
    1197     }
    1198 
    1199     function wpjam_get_chart_parameter($key){
    1200         return (WPJAM_Chart::get_instance())->get_parameter($key);
    1201     }
    1202 
    12031183    function wpjam_render_callback($cb){
    1204         if(is_array($cb)){
    1205             $cb = (is_object($cb[0]) ? get_class($cb[0]).'->' : $cb[0].'::').(string)$cb[1];
    1206         }elseif(is_object($cb)){
    1207             $cb = get_class($cb);
    1208         }
    1209 
    1210         return wpautop($cb);
     1184        return wpautop($is_array($cb) ? (is_object($cb[0]) ? get_class($cb[0]).'->' : $cb[0].'::').(string)$cb[1] : (is_object($cb) ? get_class($cb) : $cb));
    12111185    }
    12121186}
  • wpjam-basic/trunk/public/wpjam-utils.php

    r3440469 r3454739  
    202202        ['firefox', 'firefox',  '/firefox\/([\d\.]+)/i'],
    203203        ['opera',   'opera',    '/(?:opera).([\d\.]+)/i'],
    204         ['opr/',    'opera',    '/(?:opr).([\d\.]+)/i'],
     204        ['opr/',    'opera',    '/(?:opr).([\d\.]+)/i'],
    205205        ['msie',    'ie'],
    206206        ['trident', 'ie'],
     
    254254    $offset = $cache['offset'];
    255255    $index  = $cache['index'];
    256     $nip2   = pack('N', ip2long($nip));
     256    $nip2   = pack('N', ip2long($nip));
    257257    $start  = (int)$ipdot[0]*4;
    258258    $start  = unpack('Vlen', $index[$start].$index[$start+1].$index[$start+2].$index[$start+3]);
     
    310310
    311311function is_iphone(){
    312     return wpjam_get_device() == 'iPone';
     312    return wpjam_get_device() == 'iPhone';
    313313}
    314314
     
    458458
    459459    $value2 = in_array($compare, ['IN', 'BETWEEN']) ? wp_parse_list($value2) : (is_string($value2) ? trim($value2) : $value2);
    460 
    461     return [
    462         '='         => fn($a, $b)=> $strict ? $a === $b : $a == $b,
    463         '>'         => fn($a, $b)=> $a > $b,
    464         '<'         => fn($a, $b)=> $a < $b,
    465         'IN'        => fn($a, $b)=> is_array($a) ? array_all($a , fn($v)=> in_array($v, $b, $strict)) : in_array($a, $b, $strict),
    466         'BETWEEN'   => fn($a, $b)=> wpjam_between($a, ...$b)
    467     ][$compare]($value, $value2);
     460    [$a, $b]= [$value, $value2];
     461
     462    switch($compare){
     463        case '=': return $a == $b;
     464        case '>': return $a > $b;
     465        case '<': return $a < $b;
     466        case 'IN': return is_array($a) ? array_all($a , fn($v)=> in_array($v, $b, $strict)) : in_array($a, $b, $strict);
     467        case 'IBETWEENN': return wpjam_between($a, ...$b);
     468    }
     469}
     470
     471function wpjam_calc(...$args){
     472    if(wpjam_is_assoc_array($args[0])){
     473        $item       = $args[0];
     474        $formulas   = $args[1];
     475        $if_errors  = $args[2] ?? [];
     476        $item       = array_diff_key($item, $formulas);
     477
     478        foreach($formulas as $key => $formula){
     479            foreach($formula as &$t){
     480                if(str_starts_with($t, '$')){
     481                    $k  = substr($t, 1);
     482                    $v  = $item[$k] ?? null;
     483                    $r  = isset($v) ? wpjam_format($v, '-,', false) : false;
     484                    $t  = $r === false ? ($if_errors[$k] ?? null) : $r;
     485
     486                    if(!isset($t)){
     487                        $item[$key] = $if_errors[$key] ?? (isset($v) ? '!!无法计算' : '!无法计算'); break;
     488                    }
     489                }
     490            }
     491
     492            try{
     493                $item[$key] = wpjam_calc($formula);
     494            }catch(throwable $e){
     495                $item[$key] = $if_errors[$key] ?? ($e instanceof DivisionByZeroError ? '!除零错误' : '!'.$e->getMessage());
     496            }
     497        }
     498
     499        return $item;
     500    }
     501
     502    $exp    = $args[0];
     503    $item   = $args[1] ?? [];
     504    $ops    = ['+'=>'bcadd', '-'=>'bcsub', '*'=>'bcmul', '/'=>'bcdiv', '%'=>'bcmod', '**'=>'bcpow'];
     505    $throw  = fn($msg)=> wpjam_throw('invalid_calc', '计算错误:'.$msg);
     506    $calc   = [];
     507
     508    foreach(is_array($exp) ? $exp : wpjam_formula(...$args) as $t){
     509        if(is_numeric($t) || $t === '|'){
     510            $calc[] = $t;
     511        }elseif(try_remove_prefix($t, '$')){
     512            $calc[] = $item[$t] ?? 0;
     513        }elseif(in_array($t, ['sin', 'cos', 'abs', 'max', 'min', 'sqrt', 'pow', 'round', 'floor', 'ceil', 'fmod'])){
     514            $_args  = array_slice(array_splice($calc, array_last(array_keys($calc, '|'))), 1);
     515            $calc[] = $_args ? $t(...$_args) : $throw('函数「'.$t.'」无有效参数');
     516        }else{
     517            $a  = array_pop($calc);
     518            $b  = array_pop($calc);
     519
     520            if(in_array($t, ['/', '%']) && in_array((string)$a, ['0', '0.0', ''])){
     521                throw new DivisionByZeroError('Division by zero');
     522            }
     523           
     524            $calc[] = isset($ops[$t]) ? $ops[$t]((string)$b, (string)$a, 6) : wpjam_compare($b, $t, $a);
     525        }
     526    }
     527
     528    return count($calc) === 1 ? $calc[0] : $throw('计算栈剩余元素数还有'.count($calc).'个');
     529}
     530
     531function wpjam_formula(...$args){
     532    if(is_array($args[0])){
     533        $fields = array_shift($args);
     534
     535        if($args){
     536            $render = fn($key)=> '字段'.($fields[$key]['title'] ?? '').'「'.$key.'」'.'公式「'.$fields[$key]['formula'].'」';
     537            $key    = $args[0];
     538            $parsed = $args[1];
     539            $path   = $args[2] ?? [];
     540
     541            if(isset($parsed[$key])){
     542                return $parsed;
     543            }
     544
     545            if(in_array($key, $path)){
     546                wpjam_throw('invalid_formula', '公式嵌套:'.implode(' → ', wpjam_map(array_slice($path, array_search($key, $path)), fn($k)=> $render($k))));
     547            }
     548
     549            $path[]     = $key;
     550            $formula    = wpjam_formula($fields[$key]['formula'], $fields, $render($key).'错误');
     551            $parsed     = array_reduce($formula, fn($c, $t)=> try_remove_prefix($t, '$') && !empty($fields[$t]['formula']) ? wpjam_formula($fields, $t, $c, $path) : $c, $parsed);
     552
     553            return $parsed+[$key => $formula];
     554        }
     555
     556        return wpjam_reduce($fields, fn($c, $v, $k)=> empty($v['formula']) ? $c : wpjam_formula($fields, $k, $c), []);
     557    }
     558
     559    $formula    = $args[0];
     560    $fields     = $args[1] ?? [];
     561    $error      = $args[2] ?? '';
     562    $throw      = fn($msg)=> wpjam_throw('invalid_formula', $error.':'.$msg);
     563    $functions  = ['sin', 'cos', 'abs', 'ceil', 'pow', 'sqrt', 'pi', 'max', 'min', 'fmod', 'round'];
     564    $precedence = ['+'=>1, '-'=>1, '**'=>2, '*'=>2, '/'=>2, '%'=>2, '>='=>3, '<='=>3, '!='=>3, '=='=>3, '>'=>3, '<'=>3,];
     565    $signs      = implode('|', array_map(fn($v)=> preg_quote($v, '/'), [...array_keys($precedence), '(', ')', ',']));
     566    $formula    = preg_split('/\s*('.$signs.')\s*/', trim($formula), -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
     567    $output     = $stack = [];
     568    $pt         = null;
     569
     570    foreach($formula as $i => $t){
     571        $nt = $formula[$i+1] ?? null;
     572
     573        if(is_numeric($t)){
     574            $output[]   = str_ends_with($t, '.') ? $throw('无效的数字「'.$t.'」') : (float)$t;
     575        }elseif(str_starts_with($t, '$')){
     576            $output[]   = isset($fields[substr($t, 1)]) ? $t : $throw('「'.$t.'」未定义');
     577        }elseif(in_array($t, $functions, true)){
     578            $stack[]    = $t;
     579            $output[]   = '|';
     580        }elseif($t === '('){
     581            $stack[]    = $t;
     582        }elseif($t === ')' || $t === ','){
     583            if(!in_array('(', $stack, true) || ($t === ',' && ($pt === '(' || !$nt || $nt === ','))){
     584                $throw(($t === ')' ? '未匹配' : '无效').'的「'.$t.'」');
     585            }
     586
     587            while(array_last($stack) !== '('){
     588                $output[] = array_pop($stack);
     589            }
     590
     591            if($t === ')'){
     592                array_pop($stack);
     593
     594                in_array(array_last($stack), $functions) && array_push($output, array_pop($stack));
     595            }
     596        }elseif(isset($precedence[$t])){
     597            $r  = ((!$pt || in_array($pt, ['(', ','], true)) ? 1 : 0)+((!$nt || in_array($nt, [')', ','], true) || isset($precedence[$nt])) ? 2 : 0);
     598
     599            if(in_array($t, ['+', '-'], true) && $r == 1){
     600                $output[]   = 0;
     601            }elseif($r){
     602                $throw('操作符「'.$t.'」缺少操作数');
     603            }
     604
     605            while($stack && ($v = array_last($stack)) !== '('){
     606                if(in_array($v, $functions) || $precedence[$t] <= $precedence[$v]){
     607                    $output[]   = array_pop($stack);
     608                }else{
     609                    break;
     610                }
     611            }
     612
     613            $stack[] = $t;
     614        }else{
     615            $throw('无效的符号「'.$t.'」');
     616        }
     617
     618        $pt = $t;
     619    }
     620   
     621    return array_merge($output, array_reverse(in_array('(', $stack, true) ? $throw('未匹配的「(」') : $stack));
     622}
     623
     624function wpjam_format($value, $format, ...$args){
     625    if(is_array($value) && is_array($format)){
     626        return wpjam_reduce($format ?: [], fn($c, $v, $k)=> isset($c[$k]) ? wpjam_set($c, $k, wpjam_format($c[$k], ...$v)) : $c, $value);
     627    }
     628
     629    if(is_numeric($value)){
     630        if($format == ','){
     631            return number_format(trim($value), (int)($args[0] ?? 2));
     632        }elseif($format == '%'){
     633            return round($value * 100, ($args[0] ?? 2) ?: 2).'%';
     634        }elseif(!$format && $args && is_numeric($args[0])){
     635            return round($value, $args[0]);
     636        }
     637
     638        return $value / 1;
     639    }
     640
     641    if(in_array($format, ['-,', '-%'])){
     642        if(is_string($value)){
     643            $value  = str_replace(',', '', trim($value));
     644
     645            if($format == '-%' && try_remove_suffix($value, '%')){
     646                $value  = is_numeric($value) ? $value / 100 : $value;
     647            }
     648        }
     649
     650        return is_numeric($value) ? $value / 1 : ($args ? $args[0] : $value);
     651    }
     652
     653    return $value;
    468654}
    469655
     
    506692}
    507693
    508 function wpjam_matches($item, $args, $op='AND'){
     694function wpjam_matches($arr, $args, $op='AND'){
    509695    $op = strtoupper($op);
    510696
    511     return in_array($op, ['AND', 'OR', 'NOT']) ? wpjam_array($args, fn($v, $k)=> wpjam_match($item, ...(wpjam_is_assoc_array($v) ? [$v+['key'=>$k]] : [$k, $v])), $op) : false;
    512 }
    513 
    514 function wpjam_parse_show_if($if){
    515     if(wp_is_numeric_array($if) && count($if) >= 2){
    516         $keys   = count($if) == 2 ? ['key', 'value'] : ['key', 'compare', 'value'];
    517 
    518         if(count($if) > 3){
    519             if(is_array($if[3])){
    520                 $args   = $if[3];
    521 
    522                 trigger_error(var_export($args, true)); // del 2025-12-30
    523             }
    524 
    525             $if = array_slice($if, 0, 3);
    526         }
    527 
    528         return array_combine($keys, $if)+($args ?? []);
    529     }elseif(is_array($if) && !empty($if['key'])){
    530         return $if;
     697    if(!in_array($op, ['AND', 'ALL', 'OR', 'ANY', 'NOT'])){
     698        return false;
     699    }
     700
     701    if(!is_callable($args)){
     702        return wpjam_matches($args, fn($v, $k)=> wpjam_match($arr, ...(wpjam_is_assoc_array($v) ? [$v+['key'=>$k]] : [$k, $v])), $op);
     703    }
     704
     705    if(in_array($op, ['AND', 'ALL'], true)){
     706        return array_all($arr, $args);
     707    }elseif(in_array($op, ['OR', 'ANY'], true)){
     708        return array_any($arr, $args);
     709    }else{
     710        return !array_all($arr, $args);
    531711    }
    532712}
     
    544724}
    545725
    546 function wpjam_is_array_accessible($arr){
    547     return is_array($arr) || $arr instanceof ArrayAccess;
    548 }
    549 
    550 function wpjam_array($arr=null, ...$args){
    551     if(is_object($arr)){
    552         if(method_exists($arr, 'to_array')){
    553             $data   = $arr->to_array();
    554         }elseif($arr instanceof Traversable){
    555             $data   = iterator_to_array($arr);
    556         }elseif($arr instanceof JsonSerializable){
    557             $data   = $arr->jsonSerialize();
    558             $data   = is_array($data) ? $data : [];
    559         }else{
    560             $data   = [];
    561         }
    562     }else{
    563         $data   = (array)$arr;
    564     }
    565 
    566     $cb = $args && is_callable($args[0]) ? array_shift($args) : null;
    567 
     726function wpjam_array($arr=null, $cb=null, $skip_null=false){
    568727    if(!$cb){
    569         return $data;
    570     }
    571 
    572     if($args){
    573         if(in_array($args[0], ['AND', 'ALL'], true)){
    574             return array_all($arr, $cb);
    575         }elseif(in_array($args[0], ['OR', 'ANY'], true)){
    576             return array_any($arr, $cb);
    577         }elseif($args[0] === 'NOT'){
    578             return !array_all($arr, $cb);
    579         }
    580 
    581         $skip_null  = $args[0];
    582     }
    583 
    584     $skip_null  ??= false;
    585    
    586     foreach($data as $k => $v){
     728        if(is_object($arr)){
     729            if(method_exists($arr, 'to_array')){
     730                return $arr->to_array();
     731            }elseif($arr instanceof Traversable){
     732                return iterator_to_array($arr);
     733            }elseif($arr instanceof JsonSerializable){
     734                return is_array($data = $arr->jsonSerialize()) ? $data : [];
     735            }else{
     736                return [];
     737            }
     738        }
     739
     740        return (array)$arr;
     741    }
     742
     743    foreach($arr as $k => $v){
    587744        $r  = $cb($k, $v);
    588745
    589         if(is_array($r)){
    590             if(count($r) < 2 || ($skip_null && is_null($r[1]))){
    591                 continue;
    592             }
    593 
     746        if(is_scalar($r)){
     747            $k  = $r;
     748        }elseif(is_array($r) && count($r) >= 2 && !($skip_null && is_null($r[1]))){
    594749            [$k, $v]    = $r;
    595         }elseif(is_scalar($r)){
    596             $k  = $r;
    597750        }else{
    598751            continue;
     
    600753
    601754        if(is_null($k)){
    602             $new[]      = $v;
     755            $data[] = $v;
    603756        }else{
    604             $new[$k]    = $v;
    605         }
    606     }
    607 
    608     return $new ?? [];
     757            $data[$k]   = $v;
     758        }
     759    }
     760
     761    return $data ?? [];
    609762}
    610763
     
    613766}
    614767
    615 function wpjam_pick($arr, $keys){
    616     return wpjam_array($keys, fn($i, $k)=> [$k, wpjam_get($arr, $k)], true);
    617 }
    618 
    619 function wpjam_reduce($arr, $cb, $carry=null, $key='', ...$args){
    620     [$options, $depth]  = is_array($key) ? [$key, $args[0] ?? 0] : [['key'=>$key, 'max_depth'=>$args[0] ?? 0], 0];
    621 
    622     $key    = $options['key'] ?? '';
    623     $max    = $options['max_depth'] ?? null;
     768function wpjam_pick($arr, $args){
     769    return wpjam_array($args, fn($i, $k)=> [$k, wpjam_get($arr, $k)], true);
     770}
     771
     772function wpjam_entries($arr, $key=null, $value=null){
     773    $key    ??= 0;
     774    $value  ??= (int)($key === 0);
     775
     776    return wpjam_array($arr, fn($k, $v)=> [null, [$key=>$k, $value=>$v]]);
     777}
     778
     779function wpjam_column($arr, $key=null, $index=null){
     780    return wpjam_array($arr, fn($k, $v)=> [
     781        is_null($index) || $index === false ? null : ($index === true ? $k : $v[$index]),
     782        is_array($key) ? wpjam_array($key, fn($k, $v)=> [wpjam_is_assoc_array($key) ? $k : $v, wpjam_get($arr, $v)], true) : wpjam_get($v, $key)
     783    ]);
     784}
     785
     786function wpjam_map($arr, $cb, $args=[]){
     787    $args   = (is_bool($args) || $args === 'deep' ? ['deep'=>(bool)$args] : (is_string($args) ?  ['mode'=>$args] : $args))+['deep'=>false];
     788    $mode   = in_array($args['mode'] ?? '', ['vk', 'kv', 'k', 'v']) ? $args['mode'] : 'vk';
     789
     790    return wpjam_array($arr, fn($k, $v)=> [$k, ($args['deep'] && is_array($v)) ? wpjam_map($v, $cb, $args) : $cb(...array_map(fn($c) => $c === 'k' ? $k : $v, str_split($mode)))]);
     791}
     792
     793function wpjam_reduce($arr, $cb, $carry=null, ...$args){
     794    $depth  = $args[1] ?? 0;
     795    $args   = $args && is_array($args[0]) ? $args[0] : ['key'=>$args[0] ?? ''];
     796    $key    = $args['key'] ?? '';
     797    $max    = $args['max_depth'] ?? 0;
    624798
    625799    foreach(wpjam_array($arr) as $k => $v){
     
    628802        if($key && (!$max || $max > $depth+1) && is_array($v)){
    629803            $sub    = $key === true ? $v : wpjam_get($v, $key);
    630             $carry  = is_array($sub) ? wpjam_reduce($sub, $cb, $carry, $options, $depth+1) : $carry;
     804            $carry  = is_array($sub) ? wpjam_reduce($sub, $cb, $carry, $args, $depth+1) : $carry;
    631805        }
    632806    }
     
    666840
    667841    return $parse($group, 0, 0);
    668 }
    669 
    670 function wpjam_map($arr, $cb, $deep=false){
    671     return wpjam_array($arr, fn($k, $v)=>[$k, ($deep && is_array($v)) ? wpjam_map($v, $cb, true) : $cb($v, $k)]);
    672 }
    673 
    674 function wpjam_sum($items, $keys){
    675     return wpjam_fill($keys, fn($k)=> array_reduce($items, fn($sum, $item)=> $sum+(is_numeric($v = str_replace(',', '', ($item[$k] ?? 0))) ? $v : 0), 0));
    676842}
    677843
     
    9271093            $parts  = preg_split('/(['.preg_quote('[]', '/').'])/', $key, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    9281094
    929             if(count($parts) % 3 != 1) {
     1095            if(count($parts) % 3 != 1){
    9301096                return [];
    9311097            }
     
    10941260}
    10951261
    1096 function_alias('wpjam_is_array_accessible', 'array_accessible');
    1097 function_alias('wpjam_every',   'array_all');
    1098 function_alias('wpjam_some',    'array_any');
    1099 function_alias('wpjam_array',   'array_wrap');
    1100 function_alias('wpjam_get',     'array_get');
    1101 function_alias('wpjam_set',     'array_set');
    1102 function_alias('wpjam_merge',   'merge_deep');
    1103 
    11041262function wpjam_move($arr, $id, $data){
    11051263    $arr    = array_values($arr);
     
    12581416}
    12591417
    1260 function wpjam_format($value, $format, $precision=null){
    1261     if(is_numeric($value)){
    1262         if($format == '%'){
    1263             return round($value * 100, $precision ?: 2).'%';
    1264         }elseif($format == ','){
    1265             return number_format(trim($value), (int)($precision ?? 2));
    1266         }elseif(is_numeric($precision)){
    1267             return round($value, $precision);
    1268         }
    1269     }
    1270 
    1271     return $value;
    1272 }
    1273 
    12741418// 检查非法字符
    12751419function wpjam_blacklist_check($text, $name='内容'){
     
    12941438
    12951439    return $str;
    1296 }
    1297 
    1298 // Shortcode
    1299 function wpjam_do_shortcode($content, $tags, $ignore_html=false){
    1300     if($tags){
    1301         if(wpjam_is_assoc_array($tags)){
    1302             array_walk($tags, fn($cb, $tag)=> add_shortcode($tag, $cb));
    1303 
    1304             $tags   = array_keys($tags);
    1305         }
    1306 
    1307         if(array_any($tags, fn($tag)=> str_contains($content, '['.$tag))){
    1308             $content    = do_shortcodes_in_html_tags($content, $ignore_html, $tags);
    1309             $content    = preg_replace_callback('/'.get_shortcode_regex($tags).'/', 'do_shortcode_tag', $content);
    1310             $content    = unescape_invalid_shortcodes($content);
    1311         }
    1312     }
    1313 
    1314     return $content;
    1315 }
    1316 
    1317 function wpjam_parse_shortcode_attr($str, $tag){
    1318     return preg_match('/'.get_shortcode_regex((array)$tag).'/', $str, $m) ? shortcode_parse_atts($m[3]) : [];
    13191440}
    13201441
  • wpjam-basic/trunk/readme.txt

    r3437425 r3454739  
    5454== Changelog ==
    5555
     56= 6.9.2 =
     57* 开启多语言和新增函数 wpjam_translate
     58* 新增函数 wpjam_column 支持获取多列
     59* 新增函数 wpjam_entries 可以将关联数组转换成索引数组
     60* 增强函数 wpjam_chart 支持图表各种操作
     61* 新增函数 wpjam_formula 解析数学表达式
     62* 新增函数 wpjam_calc 计算数学表达式
     63
    5664= 6.9 =
    5765* 新增函数 get_term_level
     
    6472* 新增 PHP 8.5 array_first 和 array_last 兼容
    6573* wpjam_try 和 wpjam_catch 函数新增 :: 和 -> 模式的支持
    66 * wpjam_value_callback 函数新增 model 的支持
     74* wpjam_value 函数新增 model 的支持
    6775* list_table_action 增加 update_setting 功能
    6876* WPJAM_DB 和 list_table 支持 search_column
  • wpjam-basic/trunk/static/form.js

    r3423058 r3454739  
    173173                }
    174174            }else{
    175                 let label   = this.data('label') || (this.hasClass('plupload-input') ? this.val().split('/').pop() : this.val());
     175                let label   = this.data('label') || (this.hasClass('plupload-input') ? this.val().split('/').pop() : '');
    176176
    177177                label && $('<span class="query-label">'+label+'</span>').prepend($('<span class="dashicons"></span>').on('click', ()=> this.trigger('query_label'))).addClass(this.closest('.tag-input').length ? '' : this.data('class')).insertBefore(this);
  • wpjam-basic/trunk/static/script.js

    r3440469 r3454739  
    395395                    $('a.page-title-action').remove();
    396396
    397                     $('.wp-heading-inline').last().after(this.page_title_action || '');
     397                    $('.wp-heading-inline').last().after(this.page_title_action);
    398398                }
    399399
     
    807807
    808808                    update  = _.mapObject(update, (v, k)=> data[k] ? v : false);
    809 
    810                     data.search_box && $('p.search-box').empty().append($('<div>').html(data.search_box).find('.search-box').html());
    811809                }else{
    812810                    $form.attr('novalidate', 'novalidate').on('submit', _.debounce(function(){
     
    957955                        }, 300, true));
    958956                    }
     957                }
     958
     959                if(list_table.search){
     960                    if(list_table.search.box){
     961                        $('#list_table_form').find('p.search-box').remove().end().prepend(list_table.search.box);
     962                    }else{
     963                        $('p.search-box').find('input[type="search"]').val(list_table.search.term);
     964                    }
     965
     966                    list_table.search.columns && $('p.search-box').find('#search_columns').remove().end().prepend(list_table.search.columns);
    959967                }
    960968
  • wpjam-basic/trunk/static/style.css

    r3427357 r3454739  
    11.abscenter{left:50%; top:50%; transform:translate(-50%, -50%);}
    22.ui-autocomplete{z-index:1000000 !important;} /*要超过 TB z-index 才行,不然弹窗不行*/
     3
     4.wp-menu-image[class*=" ri-"]:before{display:inline-block; line-height:1; font-size:20px;}
    35
    46.green{font-size:larger; font-weight:500; color:green;}
     
    282284table.wp-list-table th.check-column .spinner{margin:4px 0 0 0;}
    283285table.wp-list-table th.check-column .spinner + input{display:none;}
     286table.wp-list-table th.column-no{width:42px;}
    284287
    285288table.wp-list-table .pending th,
  • wpjam-basic/trunk/wpjam-basic.php

    r3440469 r3454739  
    44Plugin URI: https://blog.wpjam.com/project/wpjam-basic/
    55Description: WPJAM 常用的函数和接口,屏蔽所有 WordPress 不常用的功能。
    6 Version: 6.9.1.2
     6Version: 6.9.2
    77Requires at least: 6.7
    88Tested up to: 6.9
Note: See TracChangeset for help on using the changeset viewer.