builtin('labels')) ? $labels->singular_name : $this->label; } $value = $this->builtin($key) ?? parent::__get($key); return $key == 'plural' ? ($value ?: ($this->rest_base ?: $this->name.'s')) : $value; } public function __set($key, $value){ $this->builtin($key, $value); return parent::__set($key, $value); } public function __call($method, $args){ return $this->call_dynamic_method($method, ...$args); } public function builtin($key='', ...$args){ $type = wpjam_get_builtin_type(get_class($this)); if(!$key){ return $type; } if($key != 'name' && property_exists('WP_'.$type, $key) && ($object = wpjam_call('get_'.$type.($type == 'post_type' ? '_object' : ''), $this->name)) ){ return $args ? ($object->$key = $args[0]) : $object->$key; } } public function permastruct(){ if(!($value = trim($this->get_arg('permastruct') ?: '', '/'))){ return; } $name = $this->name; $type = $this->builtin(); $hook = 'registered_'.$type.'_'.$name; $qv = $type == 'post_type' ? 'p' : 'term_id'; $iv = $qv == 'p' ? '%post_id%' : '%term_id%'; $value = str_replace( ($qv == 'p' ? ['%'.$name.'_id%', '%postname%'] : ['%'.$this->query_key.'%', '%termname%']), [$iv, '%'.$name.'%'], $value ); $this->rewrite = $this->rewrite ?: true; if(str_contains($value, $iv)){ if($qv == 'p'){ if($this->hierarchical){ return; } }else{ $this->remove_support('slug'); } $this->query_var ??= false; add_action($hook, fn()=> remove_rewrite_tag('%'.$name.'%')); add_filter($name.'_rewrite_rules', fn($rules)=> wpjam_map($rules, fn($v)=> str_replace('?'.$qv.'=', '?'.$type.'='.$name.'&'.$qv.'=', $v))); }elseif($value == '%'.$name.'%'){ if($qv != 'p'){ $this->no_base = true; return; } } add_action($hook, fn()=> add_permastruct($name, $value, $GLOBALS['wp_rewrite']->extra_permastructs[$name])); } public function labels($labels){ $l = (array)$labels; $n = $l['name']; $h = $this->hierarchical; if($this->builtin() == 'post_type'){ $m = ['all_items'=>'所有'.$n, 'archives'=>$n.'归档']; $s = ['撰写新', '写文章', ...($h ? ['页面', 'page', 'Page'] : ['文章', 'post', 'Post'])]; $r = ['添加', '添加'.$n, $n, $n, ucfirst($n)]; }else{ $m = []; $s = $h ? ['分类', 'categories', 'Categories', 'Category'] : ['标签', 'Tag', 'tag']; $r = $h ? [$n, $n.'s', ucfirst($n).'s', ucfirst($n)] : [$n, ucfirst($n), $n]; } return array_merge(wpjam_map($l, fn($v, $k)=> $m[$k] ?? (($v && $v != $n) ? str_replace($s, $r, $v) : $v)), (array)$this->labels); } public function to_array(){ $this->filter_args(); $this->permastruct(); if($this->_jam){ $type = $this->builtin(); if(($v = $this->rewrite) !== false){ $this->rewrite = (is_array($v) ? $v : [])+['with_front'=>false]; } if(is_admin() && $this->show_ui){ add_filter($type.'_labels_'.$this->name, [$this, 'labels']); } wpjam_map($this->options ?:[], fn($v, $k)=> wpjam_register_meta_option($type == 'post_type' ? 'post' : 'term', $k, $v+[$type=>$this->name])); } return $this->args; } public function add_field($key, ...$args){ return $this->process_arg('_fields', fn($v)=> array_merge($v ?: [], is_callable($key) ? [$key] : (is_array($key) ? $key : [$key => $args[0]]))); } public function remove_field($key){ return $this->delete_arg('_fields['.$key.']'); } public function get_fields($id=0, $action_key=''){ return wpjam_reduce($this->get_arg('_fields[]'), fn($c, $v, $k)=> array_merge($c, is_callable($v) ? wpjam_pack(wpjam_try($v, $id, $action_key), is_numeric($k) ? null : $k) : (wpjam_is_assoc_array($v) ? [$k => $v] : []) ), []); } public static function filter_builtin_args($args, $name, $object_type=null){ if(!static::get($name) && !empty($args['public']) && (did_action('init') || empty($args['_builtin']))){ return (static::register($name, ['_jam'=>false]+array_filter(compact('object_type'))+$args))->to_array(); } return $args; } } /** * @config menu_page, admin_load, register_json, init **/ #[config('menu_page', 'admin_load', 'register_json', 'init')] class WPJAM_Post_Type extends WPJAM_Core{ public function to_array(){ parent::to_array(); if($this->_jam){ $this->public ??= true; $this->supports ??= ['title']; if($this->hierarchical){ $this->update_arg('supports[]', 'page-attributes'); } if(!is_array($this->taxonomies)){ $this->delete_arg('taxonomies'); } if($v = $this->menu_icon){ $this->menu_icon = wpjam_prefix($v, 'dashicons-'); } } return $this->args += ['model'=>'WPJAM_Post']; } public function get_menu_slug(){ return $this->menu_slug ?? (($v = $this->get_arg('menu_page')) && is_array($v) ? (wp_is_numeric_array($v) ? array_first($v) : $v)['menu_slug'] : null); } public function get_fields($id=0, $action_key=''){ $fields = ($this->supports('images') ? [ 'images' => ['title'=>'图集', 'type'=>'mu-img', 'name'=>'meta_input[images]', 'item_type'=>'url', 'size'=>($this->images_sizes ?: [''])[0], 'max_items'=>$this->images_max_items] ] : [])+($this->supports('video') ? [ 'video' => ['title'=>'视频', 'type'=>'url', 'name'=>'meta_input[video]'] ] : [])+parent::get_fields($id, $action_key); if(in_array($action_key, ['add', 'set'])){ $fields = [ 'post_title' => ['type'=>'text', 'title'=>'标题', 'required'] ]+($action_key == 'add' ? [ 'post_type' => ['type'=>'hidden', 'value'=>$this->name], 'post_status' => ['type'=>'hidden', 'value'=>'draft'] ] : [])+($this->supports('excerpt') ? [ 'post_excerpt' => ['title'=>'摘要', 'type'=>'textarea'] ] : [])+($this->supports('thumbnail') ? [ '_thumbnail_id' => ['title'=>'头图', 'type'=>'img', 'size'=>'600x0', 'name'=>'meta_input[_thumbnail_id]'] ] : [])+wpjam_map($fields, fn($v, $k)=> (empty($v['name']) && !property_exists('WP_Post', $k) ? ['name'=>'meta_input['.$k.']'] : [])+$v); } return $fields; } public function supports($feature){ return array_any(wp_parse_list($feature), fn($f)=> post_type_supports($this->name, $f)); } public function get_taxonomies(...$args){ $filter = $args && is_array($args[0]) ? array_shift($args) : []; $output = $args[0] ?? 'objects'; $data = get_object_taxonomies($this->name); if($filter || $output == 'objects'){ $data = wpjam_fill($data, 'wpjam_get_taxonomy'); $data = $filter ? wpjam_filter($data, $filter) : $data; $data = $output == 'objects' ? $data : array_keys($data); } return $data; } public function parse_images($images, $args=[]){ $ss = $this->images_sizes; $sizes = []; foreach(['large', 'thumbnail'] as $k){ if(($v = $args[$k.'_size'] ?? '') !== false){ if(!$v && $ss){ $v = $ss[$k == 'large' ? 0 : 1]; if($k == 'thumbnail' && count($images) == 1){ $i = array_first($images); $q = wpjam_image($i, 'query') ?: wpjam_tap(wpjam_image($i, 'size') ?: ['width'=>0, 'height'=>0], fn($q)=> update_post_meta($args['post_id'], 'images', [add_query_arg($q, $i)])); $v = ($o = $q['orientation'] ?? '') ? ($ss[$o == 'landscape' ? 2 : 3] ?? $v) : $v; } }else{ $v = $v ?: $this->{$k.'_size'}; } $sizes[$k] = $v ?: $k; } } $sizes += (!$sizes || ($args['full_size'] ?? true)) ? ['full'=>'full'] : []; foreach($images as $image){ $parsed = array_map(fn($s)=> wpjam_get_thumbnail($image, $s), $sizes); $parsed = array_merge($parsed, ($query = wpjam_image($image, 'query')) ? wpjam_pick($query, ['orientation', 'width', 'height'])+['width'=>0, 'height'=>0] : []); if(isset($sizes['thumbnail'])){ $size = wpjam_parse_size($sizes['thumbnail']); $parsed = array_reduce(['width', 'height'], fn($c, $k)=> $c+['thumbnail_'.$k=>$size[$k] ?? 0], $parsed); } $results[] = count($sizes) == 1 ? array_first($parsed) : $parsed; } return $results; } public function reset_invalid_parent($value=0){ $wpdb = $GLOBALS['wpdb']; $ids = $wpdb->get_col($wpdb->prepare("SELECT p1.ID FROM {$wpdb->posts} p1 LEFT JOIN {$wpdb->posts} p2 ON p1.post_parent = p2.ID WHERE p1.post_type=%s AND p1.post_parent > 0 AND p2.ID is null", $this->name)) ?: []; return count(array_map(fn($id)=> wp_update_post(['ID'=>$id, 'post_parent'=>$value]), $ids)); } public function registered(){ $this->_jam && wpjam_init(fn()=> register_post_type($this->name, $this->to_array())); } public static function filter_clauses($clauses, $query){ $wpdb = $GLOBALS['wpdb']; $orderby = $query->get('orderby'); $order = strtoupper($query->get('order')) == 'ASC' ? 'ASC' : 'DESC';; if($orderby == 'related'){ if($tt_ids = array_filter(wp_parse_id_list($query->get('term_taxonomy_ids')))){ $clauses['join'] .= "INNER JOIN {$wpdb->term_relationships} AS tr ON {$wpdb->posts}.ID = tr.object_id"; $clauses['where'] .= " AND tr.term_taxonomy_id IN (".implode(",", $tt_ids).")"; $clauses['groupby'] .= " tr.object_id"; $clauses['orderby'] = " count(tr.object_id) DESC, {$wpdb->posts}.ID DESC"; } }elseif($orderby == 'comment_date'){ $ct = $query->get('comment_type') ?: 'comment'; $str = $ct == 'comment' ? "'comment', ''" : "'".esc_sql($ct)."'"; $where = "ct.comment_type IN ({$str}) AND ct.comment_parent=0 AND ct.comment_approved NOT IN ('spam', 'trash', 'post-trashed')"; $clauses['join'] = "INNER JOIN {$wpdb->comments} AS ct ON {$wpdb->posts}.ID = ct.comment_post_ID AND {$where}"; $clauses['groupby'] = "ct.comment_post_ID"; $clauses['orderby'] = "MAX(ct.comment_ID) {$order}"; }elseif(in_array($orderby, ['views', 'comment_type'])){ $clauses['join'] .= $wpdb->prepare("LEFT JOIN {$wpdb->postmeta} jam_pm ON {$wpdb->posts}.ID = jam_pm.post_id AND jam_pm.meta_key = %s ", sanitize_key($orderby == 'comment_type' ? $query->get('comment_count') : 'views')); $clauses['orderby'] = "(COALESCE(jam_pm.meta_value, 0)+0) {$order}, " . $clauses['orderby']; $clauses['groupby'] = "{$wpdb->posts}.ID"; }elseif(in_array($orderby, ['', 'date', 'post_date'])){ $clauses['orderby'] .= ", {$wpdb->posts}.ID {$order}"; } return $clauses; } public static function filter_results($posts, $query){ $q = &$query->query_vars; if(($sticky_posts = array_diff(wp_parse_id_list(wpjam_pull($q, 'sticky_posts') ?: []), $q['post__not_in'])) && ($stickies = get_posts([ 'orderby' => 'post__in', 'post__in' => $sticky_posts, 'post_type' => $q['post_type'] ?: 'post', 'post_status' => 'publish', 'posts_per_page' => count($sticky_posts), ]+wpjam_pick($q, ['suppress_filters', 'cache_results', 'update_post_meta_cache', 'update_post_term_cache', 'lazy_load_term_meta']))) ){ $q['sticky_posts'] = array_column($stickies, 'ID'); return array_merge($stickies, array_filter($posts, fn($post)=> !in_array($post->ID, $q['sticky_posts'], true))); } return $posts; } public static function add_hooks(){ $hook = 'content_save_pre'; $cb = 'wp_filter_post_kses'; wpjam_hook($hook, 'tap', fn($c)=> is_serialized($c) && remove_filter($hook, $cb) && wpjam_once($hook, 'tap', fn()=> add_filter($hook, $cb), 11), 1); add_filter('post_type_link', fn($link, $post)=> str_replace('%post_id%', $post->ID, $link), 1, 2); add_filter('posts_clauses', [static::class, 'filter_clauses'], 1, 2); add_filter('posts_results', [static::class, 'filter_results'], 1, 2); } } /** * @config menu_page, admin_load, register_json **/ #[config('menu_page', 'admin_load', 'register_json')] class WPJAM_Taxonomy extends WPJAM_Core{ public function to_array(){ $this->filter_args(); $this->query_key = ['category'=>'cat', 'post_tag'=>'tag_id'][$this->name] ?? $this->name.'_id'; $this->column_name = ['category'=>'categories', 'post_tag'=>'tags'][$this->name] ?? 'taxonomy-'.$this->name; $this->supports = wpjam_array($this->supports ?? ['slug', 'description', ...($this->levels == 1 ? [] : ['parent'])], fn($k, $v)=> is_numeric($k) ? [$v, true] : [$k, $v]); parent::to_array(); return $this->args += ($this->_jam ? ['show_in_nav_menus'=>false, 'show_in_rest'=>true, 'show_admin_column'=>true, 'hierarchical'=>true] : [])+['model'=>'WPJAM_Term', 'show_in_posts_rest'=>$this->show_in_rest]; } public function add_support($feature, $value=true){ return $this->update_arg('supports['.$feature.']', $value); } public function remove_support($feature){ return $this->delete_arg('supports['.$feature.']'); } public function supports($feature, ...$args){ return is_array($feature) ? array_any($feature, [$this, $supports]) : (bool)$this->get_arg('supports['.$feature.']'); } public function selectable(){ return $this->selectable ?? (wp_count_terms(['taxonomy'=>$this->name, 'hide_empty'=>false]+($this->levels > 1 ? ['parent'=>0] : [])) <= 30); } public function get_fields($id=0, $action_key=''){ $fields = ($this->supports('thumbnail') ? [ 'thumbnail' => ['title'=>'缩略图', 'size'=>$this->thumbnail_size]+array_combine(['type', 'item_type'], $this->thumbnail_type == 'image' ? ['image', 'image'] : ['img', 'url']) ] : [])+($this->supports('banner') ? [ 'banner' => ['title'=>'大图', 'type'=>'img', 'item_type'=>'url', 'size'=>$this->banner_size, 'show_if'=>['parent', -1]] ] : [])+parent::get_fields($id, $action_key); if($action_key == 'set'){ $fields = [ 'name' => ['title'=>'名称', 'type'=>'text', 'class'=>'', 'required'] ]+($this->supports('slug') ? [ 'slug' => ['title'=>'别名', 'type'=>'text', 'class'=>'', 'required'] ] : [])+($this->hierarchical && $this->levels !== 1 && $this->supports('parent') ? [ 'parent' => ['title'=>'父级', 'options'=>['-1'=>'无']+$this->get_options(apply_filters('taxonomy_parent_dropdown_args', ['exclude_tree'=>$id], $this->name, 'edit'))] ] : [])+($this->supports('slug') ? [ 'description' => ['title'=>'描述', 'type'=>'textarea'] ] : [])+$fields; } return $fields; } public function get_options($args=[]){ return array_column(wpjam_get_terms($args+['taxonomy'=>$this->name, 'hide_empty'=>0, 'format'=>'flat', 'parse'=>false]), 'name', 'term_id'); } public function get_mapping($post_id){ $post = wpjam_try('wpjam_validate_post', $post_id, $this->mapping_post_type); $data = ['name'=>$post->post_title, 'slug'=>$post->post_type.'-'.$post_id, 'taxonomy'=>$this->name]; if($term_id = get_post_meta($post_id, $this->query_key, true)){ if($term = get_term($term_id, $this->name)){ if($term->name != $data['name'] || $term->slug != $data['slug']){ WPJAM_Term::update($term_id, $data); } return $term_id; } } return wpjam_tap(WPJAM_Term::insert($data), fn($term_id)=> update_post_meta($post_id, $this->query_key, $term_id)); } public function dropdown(){ $args = ['taxonomy'=>$this->name, 'name'=>$this->query_key]; $value = $this->get_param($this->query_key); if(is_null($value)){ $slug = ($var = $this->query_var) ? $this->get_param($var) : null; $slug ??= $this->get_param('taxonomy') == $this->name ? $this->get_param('term') : ''; $term = $slug ? get_term_by('slug', $slug, $this->name) : ''; $value = $term ? $term->term_id : ''; } if($this->hierarchical){ wp_dropdown_categories($args+[ 'show_option_all' => $this->labels->all_items, 'show_option_none' => '没有设置', 'option_none_value' => 'none', 'selected' => $value, 'hierarchical' => true ]); }else{ echo wpjam_field($args+[ 'value' => $value, 'type' => 'text', 'data_type' => 'taxonomy', 'filterable' => true, 'placeholder' => '请输入'.$this->title, 'title' => '', 'class' => '' ]); } } public function registered(){ $this->_jam && wpjam_init(fn()=> register_taxonomy($this->name, $this->object_type, $this->to_array())); } public static function filter_request($vars){ if(($struct = get_option('permalink_structure')) && ($request = $GLOBALS['wp']->request) && ($no_base = static::get_by('no_base', true)) && !isset($vars['module']) && !is_admin() ){ if(preg_match("#(.?.+?)/page/?([0-9]{1,})/?$#", $request, $matches)){ [, $request, $paged] = $matches; } if($GLOBALS['wp_rewrite']->use_verbose_page_rules){ if(($vars['error'] ?? '') == '404'){ $key = 'error'; }elseif(str_contains($struct, '/%postname%') && !empty($vars['name'])){ $key = 'name'; }elseif(!str_contains($request, '/')){ $type = array_find(['author', 'category'], fn($k)=> str_starts_with($struct, '/%'.$k.'%')); $key = $type && !empty($vars[$type.'_name']) ? [$type.'_name', 'name'] : ''; } }else{ $key = !empty($vars['pagename']) && !isset($_GET['page_id']) && !isset($_GET['pagename']) ? 'pagename' : ''; } foreach(!empty($key) ? array_keys($no_base) : [] as $tax){ $name = is_taxonomy_hierarchical($tax) ? wp_basename($request) : $request; if(array_find(wpjam_get_all_terms($tax), fn($term)=> $term->slug == $name)){ return ($tax == 'category' ? ['category_name'=>$name] : ['taxonomy'=>$tax, 'term'=>$name])+array_filter(['paged'=>$paged ?? 0])+wpjam_except($vars, $key); } } } return $vars; } public static function add_hooks(){ wpjam_init(fn()=> add_rewrite_tag('%term_id%', '([0-9]+)', 'term_id=')); wpjam_hook('query_vars', '+', ['term_id'], 11); add_filter('pre_term_link', fn($link, $term)=> wpjam_get_taxonomy_setting($term->taxonomy, 'no_base') ? '%'.$term->taxonomy.'%' : str_replace('%term_id%', $term->term_id, $link), 1, 2); add_filter('request', [self::class, 'filter_request']); } } class WPJAM_Post extends WPJAM_Instance{ public function __get($key){ if(in_array($key, ['id', 'post_id'])){ return $this->id; }elseif($key == 'views'){ return (int)$this->meta_get('views'); }elseif($key == 'viewable'){ return is_post_publicly_viewable($this->id); }elseif($key == 'type_object'){ return wpjam_get_post_type_object($this->post_type); }elseif($key == 'thumbnail'){ return $this->supports('thumbnail') ? get_the_post_thumbnail_url($this->id, 'full') : ''; }elseif($key == 'images'){ return $this->supports('images') ? array_values($this->meta_get('images') ?: []) : []; } return $this->builtin($key); } public function __call($method, $args){ if($method == 'get_type_setting'){ return $this->type_object->{$args[0]}; }elseif(in_array($method, ['get_taxonomies', 'supports'])){ return $this->type_object->$method(...$args); }elseif(in_array($method, ['get_content', 'get_excerpt', 'get_first_image_url', 'get_thumbnail_url', 'get_images'])){ return ('wpjam_get_post_'.substr($method, 4))($this->post, ...$args); }elseif($cb = ['get_terms'=>'get_the_terms', 'set_terms'=>'wp_set_post_terms', 'in_term'=>'is_object_in_term'][$method] ?? ''){ return $cb($this->id, ...$args); }elseif($method === 'in_taxonomy'){ return is_object_in_taxonomy($this->post, ...$args); } return $this->call_dynamic_method($method, ...$args); } public function save($data){ if(array_find(['post_status', 'status'], fn($k)=> ($data[$k] ?? '') === 'publish')){ if($cb = wpjam_callback([$this, 'is_publishable'])){ $result = wpjam_catch($cb) ?: new WP_Error('cannot_publish', '不可发布'); } } return wpjam_then($result ?? true, fn()=> $data ? self::update($this->id, $data, false) : true); } public function set_status($status){ return $this->save(['post_status'=>$status]); } public function publish(){ return $this->set_status('publish'); } public function unpublish(){ return $this->set_status('draft'); } public function get_unserialized(){ return wpjam_unserialize($this->content, fn($fixed)=> $this->save(['content'=>$fixed])) ?: []; } public function parse_for_json($args=[]){ if($args && is_string($args) && in_array($args, ['date', 'modified'])){ $ts = get_post_timestamp($this->id, $args); $prefix = $args == 'modified' ? $args.'_' : ''; $result = [$prefix.'timestamp'=>$ts, $prefix.'time'=>wpjam_human_time_diff($ts), $prefix.'date'=>wpjam_date('Y-m-d', $ts)]; return $result+($args == 'date' ? ['day'=>wpjam_human_date_diff($result['date'])] : []); } $args += self::get_default_args(); $query = $args['query'] ?? null; $main = $query && $query->is_main_query(); $single = $query && ($query->is_single($this->id) || $query->is_page($this->id)); $rest = $single ? 'show_in_rest' : 'show_in_posts_rest'; $json = wpjam_pick($this, ['id', 'type', 'post_type', 'status', 'views'])+['icon'=>(string)$this->type_object->icon]; $json += $this->viewable ? ['name'=>urldecode($this->name), 'post_url'=>str_replace(home_url(), '', get_permalink($this->id))] : []; $json += wpjam_fill(['title', 'excerpt'], fn($k)=> $this->supports($k) ? html_entity_decode(('get_the_'.$k)($this->id)) : ''); $json += ['thumbnail'=>$this->get_thumbnail_url($args['thumbnail_size'])]; $json += $this->supports('images') ? ['images'=>$this->get_images()] : []; $json += ['user_id'=>(int)$this->author]; $json += $this->supports('author') ? ['author'=>wpjam_get_user($this->author)] : []; $json += $this->parse_for_json('date')+$this->parse_for_json('modified'); $json += $this->password ? ['password_protected'=>true, 'password_required'=>post_password_required($this->id)] : []; $json += $this->supports('page-attributes') ? ['menu_order'=>(int)$this->menu_order] : []; $json += $this->supports('post-formats') ? ['format'=>get_post_format($this->id) ?: ''] : []; if($main || $args['options_required']){ $json += array_reduce(wpjam_get_post_options($this->type, [$rest=>true]), fn($c, $o)=> $c+$o->prepare($this->id), []); } if($main || $args['taxonomy_required']){ $json += array_reduce($this->get_taxonomies([$rest=>true], 'names'), fn($c, $t)=> $c+[$t=>wpjam_get_terms(['terms'=>$this->get_terms($t), 'taxonomy'=>$t])], []); } if(($main && $single) || $args['content_required']){ if($this->supports('editor')){ $json += ['content'=>$this->get_content(), 'multipage'=>(bool)$GLOBALS['multipage']]; $json += $json['multipage'] ? ['numpages'=>$GLOBALS['numpages'], 'page'=>$GLOBALS['page']] : []; }else{ $json += is_serialized($this->content) ? ['content'=>$this->get_unserialized()] : []; } } $filter = $args['suppress_filter'] ? '' : ($main ? 'wpjam_post_json' : ($args['filter'] ?? '')); return $filter ? apply_filters($filter, $json, $this->id, $args) : $json; } public function value_callback($field){ if($field == 'tax_input'){ return wpjam_fill($this->get_taxonomies('names'), fn($tax)=> array_column($this->get_terms($tax), 'term_id')); } return $this->post->$field ?? $this->meta_get($field); } public function update_callback($data, $defaults){ return wpjam_then( $this->save(wpjam_pull($data, [...array_keys($this->data), 'tax_input'])), fn($result)=> $data ? $this->meta_input($data, $defaults) : $result ); } public static function get_default_args(){ return [ 'suppress_filter' => false, 'options_required' => true, 'taxonomy_required' => true, 'content_required' => false, 'thumbnail_size' => null ]; } public static function get_instance($post=null, $type=null, $wp_error=false){ return wpjam_then( static::validate($post ?: get_post(), $type), fn($v)=> self::instance($v->ID, fn($id)=> [wpjam_get_post_type_setting(get_post_type($id), 'model') ?: 'WPJAM_Post', 'create_instance']($id)), fn($e)=> $wp_error ? $e : null ); } public static function validate($value, $type=null){ $type ??= static::get_current_post_type(); return ($post = $value ? self::get_post($value) : null) ? ((!post_type_exists($post->post_type) || ($type && $type !== 'any' && !in_array($post->post_type, (array)$type))) ? new WP_Error('invalid_post_type') : $post ) : new WP_Error('invalid_id'); } public static function get($post){ if($data = self::get_post($post, ARRAY_A)){ $key = 'post_content'; $data = ['id'=>$data['ID']]+(is_serialized($data[$key]) ? [$key=>wpjam_unserialize($data[$key])] : [])+$data; $data += wpjam_array($data, fn($k, $v)=> try_prefix($k, '-', 'post_') ? [$k, $v] : null); } return $data; } public static function update($post_id, $data, $validate=true){ return wpjam_then( $validate ? static::validate($post_id) : null, fn()=> parent::update($post_id, $data) ); } protected static function call_method($method, ...$args){ if($method == 'insert'){ $data = $args[0]; $type = array_unique(array_filter([$data['post_type'] ?? '', static::get_current_post_type()])); $type = count($type) <= 1 ? (array_first($type) ?: 'post') : ''; $data['post_type'] = $type && post_type_exists($type) ? $type : wpjam_throw('invalid_post_type'); $data['post_status'] ??= current_user_can(get_post_type_object($type)->cap->publish_posts) ? 'publish' : 'draft'; $data['post_author'] ??= get_current_user_id(); $data['post_date'] ??= wpjam_date('Y-m-d H:i:s'); return wp_insert_post(wp_slash($data), true, true); }elseif($method == 'update'){ return wp_update_post(wp_slash(['ID'=>$args[0]]+$args[1]), true, true); }elseif($method == 'delete'){ return wp_delete_post($args[0], true) ?: wpjam_throw('delete_error', '删除失败'); } } protected static function sanitize_data($data, $post_id=0){ $key = 'post_content'; $data += wpjam_array(get_class_vars('WP_Post'), fn($k, $v)=> try_prefix($k, '-', 'post_') && isset($data[$k]) ? ['post_'.$k, $data[$k]] : null); return (is_array($data[$key] ?? '') ? [$key=>serialize($data[$key])] : [])+$data +(!$post_id && method_exists(static::class, 'is_publishable') ? ['post_status'=>'draft'] : []); } public static function get_by_ids($post_ids){ return array_map('get_post', array_filter(self::update_caches($post_ids))); } public static function update_caches($post_ids, $update_term_cache=false, $update_meta_cache=false){ if($post_ids = array_filter(wp_parse_id_list($post_ids))){ _prime_post_caches($post_ids, $update_term_cache, $update_meta_cache); $group = wpjam_group(wp_cache_get_multiple($post_ids, 'posts'), fn($v)=> $v ? 1 : 0); do_action('wpjam_deleted_ids', 'post', array_keys($group[0] ?? [])); do_action('wpjam_update_post_caches', ($group[1] ?? []), compact('update_term_cache', 'update_meta_cache')); return $group[1] ?? []; } return []; } public static function get_post($post, $output=OBJECT, $filter='raw'){ return wpjam_tap(get_post($post, $output, $filter), fn($v)=> $post && is_numeric($post) && !$v && do_action('wpjam_deleted_ids', 'post', $post)); } public static function get_current_post_type(){ if(static::class !== self::class){ return (WPJAM_Post_Type::get(static::class, 'model', self::class) ?: [])['name'] ?? null; } } public static function get_path($args, $item=[]){ $type = $item['post_type']; return $args === 'fields' ? (get_post_type_object($type) ? [$type.'_id' => self::get_field(['post_type'=>$type, 'required'=>true])] : []) : (($id = is_array($args) ? (int)($args[$type.'_id'] ?? 0) : $args) ? ($item['platform'] == 'template' ? get_permalink($id) : str_replace('%post_id%', $id, $item['path'])) : new WP_Error('invalid_id', [wpjam_get_post_type_setting($type, 'title')]) ); } public static function get_field($args){ $args['title'] ??= is_string($args['post_type'] ?? null) ? wpjam_get_post_type_setting($args['post_type'], 'title') : null; $placeholder = '请输入'.$args['title'].'ID或者输入关键字筛选'; return $args+compact('placeholder')+['type'=>'text', 'class'=>'all-options', 'data_type'=>'post_type']; } public static function with_field($method, $field, $value){ $type = $field->post_type; if($method == 'validate'){ return (is_numeric($value) && wpjam_try(static::class.'::validate', $value, $type)) ? (int)$value : null; }elseif($method == 'parse'){ return wpjam_get_post($value, ['post_type'=>$type, 'thumbnail_size'=>$field->size]); } } public static function query_items($args){ if(wpjam_pull($args, 'data_type')){ return wpjam_get_posts($args+[ 's' => $args['search'] ?? null, 'posts_per_page' => $args['number'] ?? 10, 'suppress_filters' => false, ]); } $args['post_status'] = wpjam_pull($args, 'status') ?: 'any'; $args['post_type'] ??= static::get_current_post_type(); $args['posts_per_page'] ??= $args['number'] ?? 10; return [ 'items' => $GLOBALS['wp_query']->query($args), 'total' => $GLOBALS['wp_query']->found_posts ]; } public static function query_calendar($args){ return array_reduce($GLOBALS['wp_query']->query([ 'posts_per_page' => -1, 'post_status' => wpjam_pull($args, 'status') ?: 'any' ]+$args+[ 'post_type' => static::get_current_post_type(), 'monthnum' => $args['month'] ]), fn($c, $p)=> wpjam_set($c, wpjam_at($p->post_date, ' ', 0).'[]', $p), []); } public static function get_views(){ if(!in_array(get_current_screen()->base, ['edit', 'upload'])){ $counts = array_filter((array)wp_count_posts(static::get_current_post_type())); $views = ['all'=>['filter'=>['status'=>null, 'show_sticky'=>null], 'label'=>'全部', 'count'=>array_sum($counts)]]; return wpjam_reduce($counts, fn($c, $v, $k)=> $c+(($object = get_post_status_object($k)) && $object->show_in_admin_status_list ? [$k=>['filter'=>['status'=>$k], 'label'=>$object->label, 'count'=>$v]] : []), $views); } } public static function filter_fields($fields, $id){ return ($id && !is_array($id) && !isset($fields['title']) && !isset($fields['post_title']) ? ['title'=>['title'=>wpjam_get_post_type_setting(get_post_type($id), 'title').'标题', 'type'=>'view', 'value'=>get_the_title($id)]] : [])+$fields; } public static function get_meta($post_id, ...$args){ // deprecated return wpjam_get_metadata('post', $post_id, ...$args); } public static function update_meta($post_id, ...$args){ // deprecated return wpjam_update_metadata('post', $post_id, ...$args); } public static function update_metas($post_id, $data, $meta_keys=[]){ return static::update_meta($post_id, $data, $meta_keys); } } class WPJAM_Term extends WPJAM_Instance{ public function __get($key){ if($key == 'id'){ return $this->id; }elseif($key == 'tax_object'){ return wpjam_get_taxonomy($this->taxonomy); }elseif($key == 'object_type'){ return $this->tax_object->$key ?: []; }elseif($key == 'level'){ return get_term_level($this->id); }elseif($key == 'depth'){ return get_term_depth($this->id); }elseif($key == 'link'){ return get_term_link($this->term); } return $this->builtin($key); } public function __call($method, $args){ if($method == 'get_tax_setting'){ return $this->tax_object->{$args[0]}; }elseif(in_array($method, ['get_taxonomies', 'supports'])){ return $this->tax_object->$method(...$args); }elseif(in_array($method, ['set_object', 'add_object', 'remove_object'])){ $cb = 'wp_'.$method.'_terms'; return $cb(array_shift($args), [$this->id], $this->taxonomy, ...$args); }elseif($method == 'is_object_in'){ return is_object_in_term($args[0], $this->taxonomy, $this->id); } return $this->call_dynamic_method($method, ...$args); } public function value_callback($field){ return $this->term->$field ?? $this->meta_get($field); } public function update_callback($data, $defaults){ return wpjam_then( $this->save(wpjam_pull($data, ['name', 'parent', 'slug', 'description', 'alias_of'])), fn($result)=> $data ? $this->meta_input($data, $defaults) : $result ); } public function save($data){ return $data ? self::update($this->id, $data, false) : true; } public function get_object_type(){ return $this->object_type; } public function get_thumbnail_url($size='full', $crop=1){ return wpjam_get_term_thumbnail_url($this->term, $size, $crop); } public function parse_for_json($args=[]){ $id = $this->id; $tax = $this->taxonomy; $json = ['id'=>$id, 'name'=>html_entity_decode($this->name)]+wpjam_pick($this, ['name', 'taxonomy', 'count', ...(is_taxonomy_viewable($tax) ? ['slug'] : []), ...(is_taxonomy_hierarchical($tax) ? ['parent'] : []), 'description']); return apply_filters('wpjam_term_json', array_reduce(wpjam_get_term_options($tax), fn($c, $v)=> array_merge($c, $v->prepare($id)), $json), $id); } public static function get_instance($term, $tax=null, $wp_error=false){ return wpjam_then( self::validate($term, $tax), fn($v)=> self::instance($v->term_id, fn($id)=> [wpjam_get_taxonomy_setting(get_term_taxonomy($id), 'model') ?: 'WPJAM_Term', 'create_instance']($id)), fn($e)=> $wp_error ? $e : null ); } public static function get($term){ return wpjam_then( $term ? self::get_term($term, '', ARRAY_A) : [], fn($data)=> $data ? $data+['id'=>$data['term_id']] : $data ); } public static function update($term_id, $data, $validate=true){ return wpjam_then( $validate ? static::validate($term_id) : null, fn()=> parent::update($term_id, $data) ); } protected static function call_method($method, ...$args){ if($method == 'insert'){ $data = $args[0]; $tax = array_unique(array_filter([wpjam_pull($data, 'taxonomy'), static::get_current_taxonomy()])); $tax = count($tax) == 1 ? array_first($tax) : null; return wp_insert_term(wp_slash(wpjam_pull($data, 'name')), $tax, wp_slash($data))['term_id']; }elseif($method == 'update'){ $data = $args[1]; $tax = wpjam_pull($data, 'taxonomy') ?: get_term_field('taxonomy', $args[0]); return wp_update_term($args[0], $tax, wp_slash($data)); }elseif($method == 'delete'){ return wp_delete_term($args[0], get_term_field('taxonomy', $args[0])); } } public static function get_meta($term_id, ...$args){ // deprecated return wpjam_get_metadata('term', $term_id, ...$args); } public static function update_meta($term_id, ...$args){ // deprecated return wpjam_update_metadata('term', $term_id, ...$args); } public static function update_metas($term_id, $data, $meta_keys=[]){ // deprecated return self::update_meta($term_id, $data, $meta_keys); } public static function get_by_ids($term_ids){ return self::update_caches($term_ids); } public static function update_caches($term_ids){ if($term_ids = array_filter(wp_parse_id_list($term_ids))){ _prime_term_caches($term_ids); $group = wpjam_group(wp_cache_get_multiple($term_ids, 'terms'), fn($v)=> $v ? 1 : 0); do_action('wpjam_deleted_ids', 'term', array_keys($group[0] ?? [])); return $group[1] ?? []; } return []; } public static function get_term($term, $tax='', $output=OBJECT, $filter='raw'){ return wpjam_tap(get_term($term, $tax, $output, $filter), fn($v)=> $term && is_numeric($term) && !$v && do_action('wpjam_deleted_ids', 'term', $term)); } public static function get_current_taxonomy(){ if(static::class !== self::class){ return (WPJAM_Taxonomy::get(static::class, 'model', self::class) ?: [])['name'] ?? null; } } public static function get_path($args, $item=[]){ $tax = $item['taxonomy']; $key = wpjam_get_taxonomy_setting($tax, 'query_key'); return $args === 'fields' ? ($key ? [$key=> self::get_field(['taxonomy'=>$tax, 'required'=>true])] : []) : (($id = is_array($args) ? (int)wpjam_get($args, $key) : $args) ? ($item['platform'] == 'template' ? get_term_link($id) : str_replace('%term_id%', $id, $item['path'])) : new WP_Error('invalid_id', [wpjam_get_taxonomy_setting($tax, 'title')]) ); } public static function get_field($args=[]){ $object = isset($args['taxonomy']) && is_string($args['taxonomy']) ? wpjam_get_taxonomy($args['taxonomy']) : null; $type = $args['type'] ?? ''; $title = $args['title'] ??= $object ? $object->title : null; $args += ['data_type'=>'taxonomy']; if($object && $object->hierarchical){ if(!$type && is_admin() && $object->levels > 1 && $object->selectable()){ $type = 'cascade'; }elseif(!$type || ($type == 'mu-text' && empty($args['item_type']))){ $type = (!is_admin() || $object->selectable()) ? ($type ? 'mu-' : '').'select' : $type; }elseif($type == 'mu-text' && $args['item_type'] == 'select'){ $type = 'mu-select'; } } if(in_array($type, ['select', 'mu-select', 'cascade'])){ if(($k = 'show_option_all') && ($v = array_first(wpjam_pull($args, [$k, 'option_all']))) !== false){ $args[$k] = $v === true || is_null($v) ? '请选择' : $v; } return ['type'=>$type]+($type == 'cascade' ? ['filter_key'=>'parent'] : ['options'=> fn()=> $object->get_options()])+$args; } return $args+['type'=>'text', 'class'=>'all-options', 'placeholder'=>'请输入'.$title.'ID或者输入关键字筛选']; } public static function with_field($method, $field, $value){ $tax = $field->taxonomy; if($method == 'render'){ $terms = get_terms(['taxonomy'=>$tax, 'hide_empty'=>0]); $values = $value ? array_reverse([$value, ...get_ancestors($value, $tax, 'taxonomy')]) : []; for($i = 0; $i < wpjam_get_taxonomy_setting($tax, 'levels'); $i++){ $parent = $i ? ($values[$i-1] ?? null) : 0; $options[] = is_null($parent) ? [] : array_column(wp_list_filter($terms, ['parent'=>$parent]), 'name', 'term_id'); } return ['value'=>$values, 'options'=>$options]; }elseif($method == 'validate'){ if(is_array($value)){ $value = array_first(wpjam_sort(array_filter($value), 'kr')) ?: 0; } if(is_numeric($value)){ return !$value || wpjam_try('get_term', $value, $tax) ? (int)$value : null; } $result = term_exists($value, $tax); return $result ? (is_array($result) ? $result['term_id'] : $result) : ($field->creatable ? wpjam_try('WPJAM_Term::insert', ['name'=>$value, 'taxonomy'=>$tax]) : null); }elseif($method == 'parse'){ return wpjam_get_term($value, $tax); } } public static function query_items($args){ if(wpjam_pull($args, 'data_type')){ return array_values(get_terms($args+['number'=>(isset($args['parent']) ? 0 : 10), 'hide_empty'=>false])); } $defaults = ['hide_empty'=>false, 'taxonomy'=>static::get_current_taxonomy()]; return ['items'=>get_terms($args+$defaults), 'total'=>wp_count_terms($defaults)]; } public static function validate($id, $tax=null){ $tax ??= self::get_current_taxonomy(); return wpjam_then(self::get_term($id), fn($v)=> (!$v || !($v instanceof WP_Term)) ? new WP_Error('invalid_term_id') : (($tax && $tax !== 'any' && !in_array($v->taxonomy, (array)$tax)) ? new WP_Error('invalid_taxonomy') : $v) ); } public static function filter_fields($fields, $id){ return ($id && !is_array($id) && !isset($fields['name']) ? ['name'=>['title'=>wpjam_get_taxonomy_setting(get_term_field('taxonomy', $id), 'title'), 'type'=>'view', 'value'=>get_term_field('name', $id)]] : [])+$fields; } } class WPJAM_User extends WPJAM_Instance{ public function __get($key){ if(in_array($key, ['id', 'user_id'])){ return $this->id; }elseif($key == 'role'){ return array_first($this->roles); }elseif($key === 'data'){ return get_userdata($this->id); } return $this->builtin($key); } public function value_callback($field){ return $this->$field; } public function save($data){ return $data ? self::update($this->id, $data) : true; } public function parse_for_json($size=96){ return apply_filters('wpjam_user_json', [ 'id' => $this->id, 'nickname' => $this->nickname, 'name' => $this->display_name, 'display_name' => $this->display_name, 'avatar' => get_avatar_url($this->user, $size), ], $this->id); } public function add_role($role, $blog_id=0){ return $role ? wpjam_call_for_blog($blog_id, fn()=> $this->roles && !in_array($role, $this->roles) ? new WP_Error('error', '你已有权限,如果需要更改权限,请联系管理员直接修改。') : wpjam_tap($this, fn()=> $this->user->add_role($role))) : $this; } public function login(){ wp_set_auth_cookie($this->id, true, is_ssl()); wp_set_current_user($this->id); do_action('wp_login', $this->user_login, $this->user); return $this; } public static function get_instance($id, $wp_error=false){ return wpjam_then( self::validate($id), fn($v)=> self::instance($v->ID, fn($id)=> new self($id)), fn($e)=> $wp_error ? $e : null ); } public static function validate($user_id){ return ($user_id && ($user = self::get_user($user_id)) && ($user instanceof WP_User)) ? $user : new WP_Error('invalid_user_id'); } public static function update_caches($ids){ $ids = array_filter(wp_parse_id_list($ids)); return $ids && (cache_users($ids) || true) ? array_map('get_userdata', $ids) : []; } public static function get_by_ids($ids){ return self::update_caches($ids); } public static function get_user($user){ return $user && is_numeric($user) ? wpjam_tap(get_userdata($user), fn($v)=> !$v && do_action('wpjam_deleted_ids', 'user', $user)) : $user; } public static function get_authors($args=[]){ return get_users(array_merge($args, ['capability'=>'edit_posts'])); } public static function get_path($args, $item=[]){ return $args === 'fields' ? ['author' => ['type'=>'select', 'options'=>fn()=> wp_list_pluck(WPJAM_User::get_authors(), 'display_name', 'ID')]] : (($id = is_array($args) ? (int)wpjam_get($args, 'author') : $args) ? ($item['platform'] == 'template' ? get_author_posts_url($id) : str_replace('%author%', $id, $item['path'])) : new WP_Error('invalid_author', ['作者']) ); } public static function options_callback($field){ return wp_list_pluck(self::get_authors(), 'display_name', 'ID'); } public static function get($id){ return ($user = get_userdata($id)) ? $user->to_array() : []; } protected static function call_method($method, ...$args){ if($method == 'create'){ $args = $args[0]+[ 'user_pass' => wp_generate_password(12, false), 'user_login' => '', 'user_email' => '', 'nickname' => '', // 'avatarurl' => '', ]; if(!wpjam_pull($args, 'users_can_register', get_option('users_can_register'))){ return new WP_Error('registration_closed', '用户注册关闭,请联系管理员手动添加!'); } if(empty($args['user_email'])){ return new WP_Error('empty_user_email', '用户的邮箱不能为空。'); } $args['user_login'] = preg_replace('/\s+/', '', sanitize_user($args['user_login'], true)); $lock_key = $args['user_login'] ? $args['user_login'].'_register_lock' : ''; if($lock_key && wpjam_lock($lock_key, 5, 'users')){ return new WP_Error('error', '该用户名正在注册中,请稍后再试!'); } $data = wpjam_pick($args, ['user_login', 'user_pass', 'user_email', 'role']); $data += $args['nickname'] ? ['nickname'=>$args['nickname'], 'display_name'=>$args['nickname']] : []; $id = static::insert($data); if($lock_key){ wpjam_lock($lock_key, -1, 'users'); } return wpjam_then($id, fn()=> static::get_instance($id)); }elseif($method == 'insert'){ return wp_insert_user(wp_slash($args[0])); }elseif($method == 'update'){ return wp_update_user(wp_slash(array_merge($args[1], ['ID'=>$args[0]]))); }elseif($method == 'delete'){ return wp_delete_user($args[0]); } } public static function create($args){ return wpjam_call_for_blog(wpjam_get($args, 'blog_id'), fn()=> static::call_method('create', $args)); } public static function query_items($args){ if(wpjam_pull($args, 'data_type')){ return get_users(array_merge($args, ['search'=> !empty($args['search']) ? '*'.$args['search'].'*' : ''])); } } public static function filter_fields($fields, $id){ if($id && !is_array($id)){ $object = self::get_instance($id); $fields = array_merge(['name'=>['title'=>'用户', 'type'=>'view', 'value'=>$object->display_name]], $fields); } return $fields; } } class WPJAM_Bind extends WPJAM_Register{ public function __construct($type, $appid){ parent::__construct($type.':'.$appid, [ 'type' => $type, 'appid' => $appid, 'bind_key' => wpjam_join('_', $type, $appid), 'domain' => $type == 'phone' ? 'phone.sms' : $appid.'.'.$type ]); } public function get_appid(){ return $this->appid; } public function get_openid($meta_type, $object_id){ return get_metadata($meta_type, $object_id, $this->bind_key, true); } public function update_openid($meta_type, $object_id, $openid){ if(!$openid){ return delete_metadata($meta_type, $object_id, $this->bind_key); } return update_metadata($meta_type, $object_id, $this->bind_key, $openid); } public function bind_openid($meta_type, $object_id, $openid){ $bound = ($current = $this->get_openid($meta_type, $object_id)) && $current != $openid; if($bound = $bound ?: wpjam_then($this->get_by_openid($meta_type, $openid), fn($v)=> $v && $v->id != $object_id)){ return wpjam_then($bound, new WP_Error('is_bound', '已绑定其他账号,请先解绑再试!')); } $this->update_value($openid, $meta_type.'_id', $object_id); $this->update_openid($meta_type, $object_id, $openid); return $openid; } public function unbind_openid($meta_type, $object_id, $openid=null){ $openid = $openid ?: $this->get_openid($meta_type, $object_id); if($openid = $openid ?: wpjam_call_method($this, 'get_openid_by', $meta_type.'_id', $object_id)){ $this->update_openid($meta_type, $object_id, ''); $this->update_value($openid, $meta_type.'_id', 0); } return $openid; } public function get_by_openid($meta_type, $openid){ if(!$this->get_value($openid)){ return new WP_Error('invalid_openid'); } $cb = 'wpjam_get_'.$meta_type.'_object'; $object = wpjam_call($cb, $this->get_value($openid, $meta_type.'_id')); $object ??= ($meta = wpjam_get_by_meta($meta_type, $this->bind_key, $openid)) ? wpjam_call($cb, array_first($meta)[$meta_type.'_id']) : null; return $object ?: (($meta_type == 'user' && ($user_id = username_exists($openid))) ? wpjam_get_user($user_id, 'object') : null); } public function get_by_user_email($meta_type, $email){ if($email && try_suffix($email, '-', '@'.$this->domain)){ return $this->get_value($email, $meta_type.'_id'); } } protected function get_value($openid, $key=null){ return wpjam_get(method_exists($this, 'get_user') ? $this->get_user($openid) : ['openid'=>$openid], $key ?: null); } protected function update_value($openid, $key, $value){ return method_exists($this, 'update_user') ? $this->update_user($openid, [$key=>$value]) : true; } public function get_user_email($openid){ return $openid.'@'.$this->domain; } public function get_email($openid){ return $this->get_user_email($openid); } public function get_avatarurl($openid){ return $this->get_value($openid, 'avatarurl'); } public function get_nickname($openid){ return $this->get_value($openid, 'nickname'); } public function get_unionid($openid){ return $this->get_value($openid, 'unionid'); } public function get_phone_data($openid){ return ($phone = $this->get_value($openid, 'phone')) ? ['phone'=>$phone, 'country_code'=>$this->get_value($openid, 'country_code') ?: 86] : []; } } class WPJAM_User_Signup extends WPJAM_Register{ public function __call($method, $args){ return [wpjam_get_bind($this->type, $this->appid), $method](...(str_ends_with($method, '_openid') ? ['user', ...$args] : $args)); } public function signup($data, $args=null){ if(is_array($data)){ $by = $this->by; $args ??= ($data['args'] ?? '') ?: []; $key = $by == 'code' ? '' : ($data[$by] ?? ''); $user = apply_filters('wpjam_user_signup', null, $by, $key, $data['code'] ?? '') ?: $this->signup($this->verify($data), $args); return wpjam_tap($user, null, fn()=> do_action('wpjam_user_signup_failed', $by, $key, $user)); } $openid = $data; $args = apply_filters('wpjam_user_signup_args', $args ?? [], $this->type, $this->appid, $openid); $user = wpjam_then($openid, fn()=> wpjam_then( $this->get_by_openid($openid), fn($user)=> $user ? $user->add_role($args['role'] ?? '', $args['blog_id'] ?? 0) : WPJAM_User::create([ 'user_login' => $openid, 'user_email' => $this->get_user_email($openid), 'nickname' => $this->get_nickname($openid) ]+$args) )); return wpjam_tap($user, fn()=> $this->bind($openid, $user->id) && do_action('wpjam_user_signuped', $user->login()->data, $args)); } public function bind($data, $user_id=null){ $user = wpjam_get_user($user_id ?? get_current_user_id(), 'object'); $openid = is_array($data) ? $this->verify($data) : $data; $openid = wpjam_then($openid, fn()=> $this->bind_openid($user->id, $openid)); if(!wpjam_if_error($openid, '')){ if(($avatarurl = $this->get_avatarurl($openid)) && $avatarurl !== $user->avatarurl){ $user->meta_input('avatarurl', $avatarurl); } if(($nickname = $this->get_nickname($openid)) && $nickname !== $user->nickname){ $user->save(['nickname'=>$nickname, 'display_name'=>$nickname]); } } return $openid; } public function unbind($user_id=null){ return $this->unbind_openid($user_id ?? get_current_user_id()); } public function verify($data){ return $this->qrcode ? wpjam_then($this->qrcode->verify($data['scene'], $data['code']), fn($qrcode)=> $qrcode['openid']) : $this->call('verify', $data); } public function get_fields($action='login', $for='admin'){ if($action == 'bind' && ($openid = $this->get_openid(get_current_user_id()))){ $view = wpjam_join("
", [ ($avatar = $this->get_avatarurl($openid)) ? '' : '', ($nickname = $this->get_nickname($openid)) ? ''.$nickname.'' : '' ]); return [ 'view' => ['type'=>'view', 'title'=>'已绑定', 'value'=>($view ?: $openid)], 'action' => ['type'=>'hidden', 'value'=>'unbind'], ]; } $fields = $this->qrcode ? wpjam_then($this->qrcode->get_fields($action), fn($v)=> wpjam_set($v, 'qrcode[title]', $this->title)) : ((is_callable($this->fields) ? $this->call('fields', $action, $for) : $this->fields) ?: []); return wpjam_then($fields, $fields+['action' => ['type'=>'hidden', 'value'=>$action]]); } public function get_attr($action='login', $for=''){ return wpjam_then($this->get_fields($action, $for), fn($fields)=> array_merge( $action == 'bind' ? ['submit_text'=>$fields['action']['value'] == 'unbind' ? '解除绑定' : '立刻绑定'] : [], $for != 'admin' ? wpjam_ajax($this->name.'-signup')->to_array() : [], ['fields'=>$for != 'admin' ? wpjam_fields($fields)->render(['wrap_tag'=>'p']) : $fields] )); } public function get_form(){ return $this->get_attr('bind', 'admin')+[ 'callback' => [$this, 'callback'], 'capability' => 'read', 'validate' => true, 'response' => 'redirect' ]; } public function callback($data){ $action = wpjam_pull($data, 'action'); return try_prefix($action, '-', 'get-') ? $this->get_attr($action) : wpjam_then(wpjam_catch([$this, $action == 'login' ? 'signup' : $action], ...($action == 'unbind' ? [] : [$data])), true); } public function registered(){ wpjam_ajax($this->name.'-signup', [ 'nopriv' => true, 'callback' => [$this, 'callback'], 'nonce_action' => fn($data)=> str_starts_with($data['action'] ?? '', 'get-') ? '' : $this->name.'-signup' ]); } public static function create($type, $args){ if(wpjam_get_bind($type, $args['appid'] ?? '')){ $model = wpjam_pull($args, 'model') ?: 'WPJAM_User_Signup'; $model = is_object($model) ? get_class($model) : $model; // 兼容 return self::register(new $model($type, $args+['type'=>$type])); } } public static function on_admin_init(){ $objects = self::get_registereds(); $tabs = wpjam_array($objects, fn($k, $v)=> $v->bind ? [$k, [ 'title' => $v->title, 'capability' => 'read', 'function' => 'form', 'form' => [$v, 'get_form'] ]] : null); $tabs && wpjam_add_menu_page([ 'parent' => 'users', 'menu_slug' => 'wpjam-bind', 'menu_title' => '账号绑定', 'order' => 20, 'capability' => 'read', 'function' => 'tab', 'tabs' => $tabs ]); $objects && wpjam_add_admin_load([ 'base' => 'users', 'callback' => fn()=> wpjam_register_list_table_column('openid', [ 'title' => '绑定账号', 'order' => 20, 'callback' => fn($user_id)=> wpjam_join('

', wpjam_map($objects, fn($v)=> ($openid = $v->get_openid($user_id)) ? $v->title.':
'.$openid : '')) ]) ]); } public static function on_login_init(){ $action = ($_REQUEST['action'] ?? '') ?: 'login'; $type = $_REQUEST[$action.'_type'] ?? ''; if(in_array($action, ['login', 'bind']) && ($objects = self::get_registereds([$action=>true]))){ if($action == 'login'){ $type = $type ?: apply_filters('wpjam_default_login_type', $action); $type = $type ?: ($_SERVER['REQUEST_METHOD'] == 'POST' ? 'login' : array_key_first($objects)); if(isset($objects[$type])){ wpjam_call($objects[$type]->login_action); } if(empty($_COOKIE[TEST_COOKIE])){ $_COOKIE[TEST_COOKIE] = 'WP Cookie check'; } $objects['login'] = '使用账号和密码登录'; }else{ is_user_logged_in() ? wpjam_hook('login_display_language_dropdown', false) : wp_die('登录之后才能执行绑定操作!'); } $type = $type && isset($objects[$type]) ? $type : array_key_first($objects); $tag = wpjam_tag('p', ['class'=>'types', 'data'=>['action'=>$action]])->append(wpjam_map($objects, fn($v, $k)=>['a', [ 'class' => $type == $k ? 'current' : '', 'data' => ['type'=>$k]+($k == 'login' ? [] : ['action'=>$k.'-signup', 'data'=>['action'=>'get-'.$action]]) ], $k == 'login' ? $v : ($action == 'bind' ? '绑定'.$v->title : $v->login_title)])); wp_enqueue_script('wpjam-login', wpjam_url(dirname(__DIR__).'/static/login.js'), ['wpjam-ajax']); wpjam_echo($tag, 'login_form'); } wp_add_inline_style('login', join("\n", [ '.login .message, .login #login_error{margin-bottom: 0;}', '.code_wrap label:last-child{display:flex;}', '.code_wrap input.button{margin-bottom:10px;}', '.login form .input, .login input[type=password], .login input[type=text]{font-size:20px; margin-bottom:10px;}', 'p.types{line-height:2; float:left; clear:left; margin-top:10px;}', 'p.types a{text-decoration: none; display:block;}', 'p.types a.current{display:none;}', 'div.fields{margin-bottom:10px;}', ])); } public static function add_hooks(){ if(wp_using_ext_object_cache()){ add_action('login_init', [self::class, 'on_login_init']); add_action('wpjam_admin_init', [self::class, 'on_admin_init']); } } }