'frontglut_moderate' ) ); Ajax::register( 'post_reject', array( $this, 'ajax_reject' ), array( 'cap' => 'frontglut_moderate' ) ); Ajax::register( 'term_save', array( $this, 'ajax_term_save' ), array( 'cap' => 'frontglut_moderate' ) ); Ajax::register( 'term_delete', array( $this, 'ajax_term_delete' ), array( 'cap' => 'frontglut_moderate' ) ); add_action( 'wp', array( $this, 'count_view' ) ); add_filter( 'frontglut_menu_items', array( $this, 'menu' ) ); add_filter( 'frontglut_quick_create', array( $this, 'quick_create' ) ); add_filter( 'frontglut_widgets', array( $this, 'widgets' ) ); add_filter( 'frontglut_menu_groups', function ( $g ) { return $g; } ); } /* ---------------- settings ---------------- */ public function defaults( $d ) { $d['posts'] = array( 'edit_statuses' => array( 'draft', 'pending', 'publish' ), 'reedit_pending' => 0, 'soft_delete' => 1, 'allow_delete' => 1, 'dup_guard' => 1, 'show_view_count' => 1, ); return $d; } public function schema( $s ) { $s['posts'] = array( 'label' => __( 'Post submission', 'frontglut' ), 'order' => 27, 'fields' => array( 'posts.edit_statuses' => array( 'type' => 'checkboxes', 'label' => __( 'Authors may edit posts while in status', 'frontglut' ), 'options' => array( 'draft' => 'Draft', 'pending' => 'Pending', 'publish' => 'Published', 'private' => 'Private' ) ), 'posts.reedit_pending' => array( 'type' => 'checkbox', 'label' => __( 'Edits to published posts go back to pending review (non-moderators)', 'frontglut' ) ), 'posts.allow_delete' => array( 'type' => 'checkbox', 'label' => __( 'Authors may delete their own posts', 'frontglut' ) ), 'posts.soft_delete' => array( 'type' => 'checkbox', 'label' => __( 'Deleting moves to trash first', 'frontglut' ) ), 'posts.dup_guard' => array( 'type' => 'checkbox', 'label' => __( 'Block duplicate titles from the same author within 24h', 'frontglut' ) ), 'posts.show_view_count' => array( 'type' => 'checkbox', 'label' => __( 'Count and show views of submitted posts', 'frontglut' ) ), ) ); return $s; } /* ---------------- dashboard hooks ---------------- */ public function menu( $i ) { $i['posts'] = array( 'label' => __( 'My posts', 'frontglut' ), 'icon' => 'admin-post', 'group' => 'content', 'order' => 10, 'cap' => 'frontglut_manage_posts', 'render' => function () { return $this->manage_screen( 'posts', 'post', __( 'My posts', 'frontglut' ), 0 ); } ); $i['moderation'] = array( 'label' => __( 'Moderation', 'frontglut' ), 'icon' => 'yes-alt', 'group' => 'content', 'order' => 12, 'cap' => 'frontglut_moderate', 'render' => array( $this, 'render_moderation' ), 'badge' => array( $this, 'pending_count' ) ); $i['terms'] = array( 'label' => __( 'Categories & tags', 'frontglut' ), 'icon' => 'tag', 'group' => 'content', 'order' => 14, 'cap' => 'frontglut_moderate', 'render' => array( $this, 'render_terms' ) ); return $i; } public function quick_create( $q ) { $form = Forms::default_form( 'post_member' ); if ( $form ) { $q[] = array( 'label' => __( 'Post', 'frontglut' ), 'url' => Dashboard::url( 'posts', array( 'a' => 'new' ) ), 'cap' => 'frontglut_manage_posts' ); } return $q; } public function widgets( $w ) { $w['my_posts'] = array( 'title' => __( 'My posts', 'frontglut' ), 'order' => 10, 'size' => 1, 'cap' => 'frontglut_manage_posts', 'render' => function () { $c = $this->counts( get_current_user_id() ); $h = '
' . Ui::stat( __( 'Published', 'frontglut' ), (int) $c['publish'] ) . Ui::stat( __( 'Pending', 'frontglut' ), (int) $c['pending'] ) . Ui::stat( __( 'Drafts', 'frontglut' ), (int) $c['draft'] ) . '
'; return $h . '

' . Ui::btn( __( 'New post', 'frontglut' ), Dashboard::url( 'posts', array( 'a' => 'new' ) ), 'frontglut-primary frontglut-sm' ) . ' ' . Ui::btn( __( 'View all', 'frontglut' ), Dashboard::url( 'posts' ), 'frontglut-sm' ) . '

'; } ); $w['moderation'] = array( 'title' => __( 'Awaiting review', 'frontglut' ), 'order' => 11, 'size' => 1, 'cap' => 'frontglut_moderate', 'render' => function () { $n = $this->pending_count(); return '
' . Ui::stat( __( 'Pending submissions', 'frontglut' ), (int) $n ) . '

' . Ui::btn( __( 'Review now', 'frontglut' ), Dashboard::url( 'moderation' ), 'frontglut-sm' ) . '

'; } ); return $w; } private function counts( $uid, $type = 'post' ) { global $wpdb; $rows = $wpdb->get_results( $wpdb->prepare( "SELECT post_status, COUNT(*) c FROM {$wpdb->posts} WHERE post_author = %d AND post_type = %s AND post_status IN ('publish','pending','draft','private','trash') GROUP BY post_status", $uid, $type ), OBJECT_K ); $out = array( 'publish' => 0, 'pending' => 0, 'draft' => 0, 'private' => 0, 'trash' => 0 ); foreach ( $rows as $s => $r ) { $out[ $s ] = (int) $r->c; } return $out; } public function managed_types() { return apply_filters( 'frontglut_managed_post_types', array( 'post' ) ); } public function pending_count() { $q = new \WP_Query( array( 'post_type' => $this->managed_types(), 'post_status' => 'pending', 'fields' => 'ids', 'posts_per_page' => 1, 'no_found_rows' => false ) ); return (int) $q->found_posts; } /* ---------------- permissions ---------------- */ public static function can_edit( $post_id, $user_id = 0 ) { $user_id = $user_id ? $user_id : get_current_user_id(); $p = get_post( $post_id ); if ( ! $p || ! $user_id ) { return false; } if ( Caps::can( 'frontglut_moderate', $user_id ) || user_can( $user_id, 'edit_others_posts' ) ) { $ok = true; } else { $ok = (int) $p->post_author === (int) $user_id && Caps::can( 'frontglut_manage_posts', $user_id ) && in_array( $p->post_status, (array) Settings::get( 'posts.edit_statuses', array( 'draft', 'pending', 'publish' ) ), true ); } return (bool) apply_filters( 'frontglut_can_edit_post', $ok, $p, $user_id ); } private function can_delete( $post_id ) { $p = get_post( $post_id ); if ( ! $p ) { return false; } if ( Caps::can( 'frontglut_moderate' ) ) { return true; } return (int) $p->post_author === get_current_user_id() && Caps::can( 'frontglut_manage_posts' ) && Settings::get( 'posts.allow_delete' ); } /* ---------------- form rendering ---------------- */ private function values_from_post( array $form, \WP_Post $post ) { $v = array(); foreach ( $form['fields'] as $f ) { $k = $f['key']; switch ( $f['storage'] ) { case 'post_title': $v[ $k ] = $post->post_title; break; case 'post_content': $v[ $k ] = $post->post_content; break; case 'post_excerpt': $v[ $k ] = $post->post_excerpt; break; case 'featured': $v[ $k ] = get_post_thumbnail_id( $post->ID ); break; case 'gallery': $v[ $k ] = array_filter( array_map( 'absint', (array) get_post_meta( $post->ID, '_frontglut_' . $k, true ) ) ); break; case 'tax': $tax = isset( $f['taxonomy'] ) ? $f['taxonomy'] : 'category'; $ts = wp_get_object_terms( $post->ID, $tax ); $v[ $k ] = ( isset( $f['ui'] ) && 'tags' === $f['ui'] ) ? wp_list_pluck( is_wp_error( $ts ) ? array() : $ts, 'name' ) : wp_list_pluck( is_wp_error( $ts ) ? array() : $ts, 'term_id' ); break; default: $m = get_post_meta( $post->ID, $k, true ); $v[ $k ] = $m; if ( in_array( $f['type'], array( 'file', 'image' ), true ) ) { $v[ $k ] = $m ? (array) $m : array(); } } } return $v; } public function form_html( array $form, $post = null ) { $s = $form['settings']; $edit = $post instanceof \WP_Post; $fields = $form['fields']; foreach ( $fields as &$f ) { if ( 'editor' === $f['type'] ) { $f['editor'] = isset( $s['editor'] ) ? $s['editor'] : 'classic'; } } unset( $f ); $vals = $edit ? $this->values_from_post( $form, $post ) : array(); $h = Ui::form_open( 'form_submit', array( 'after' => $edit ? 'redirect' : ( is_user_logged_in() ? 'redirect' : 'reset' ), 'class' => 'frontglut-post-form' ) ) . Ui::hidden( 'form_id', $form['id'] ) . Forms::honeypot(); if ( $edit ) { $h .= Ui::hidden( 'post_id', $post->ID ); } if ( ! is_user_logged_in() ) { $h .= Fields::render( array( 'key' => 'guest_name', 'type' => 'text', 'label' => __( 'Your name', 'frontglut' ), 'required' => 1, 'width' => '50' ), '', 'guest_name' ); $h .= Fields::render( array( 'key' => 'guest_email', 'type' => 'email', 'label' => __( 'Your email', 'frontglut' ), 'required' => 1, 'width' => '50' ), '', 'guest_email' ); } $h .= Forms::render_fields( $fields, $vals, 'frontglut_f', $form['id'] ); if ( ! empty( $s['captcha'] ) && Captcha::enabled() ) { $h .= Captcha::render(); } return Ui::kses( apply_filters( 'frontglut_post_form_html', $h . Ui::form_close( $edit ? __( 'Update', 'frontglut' ) : __( 'Submit', 'frontglut' ) ), $form, $post ) ); } public function sc_form( $atts ) { $a = shortcode_atts( array( 'id' => 0 ), $atts ); $form = $a['id'] ? Forms::get( (int) $a['id'] ) : Forms::default_form( is_user_logged_in() ? 'post_member' : 'post_guest' ); if ( ! $form || 'post' !== $form['type'] ) { return ''; } Assets::need(); $s = $form['settings']; if ( empty( $s['guest'] ) && ! is_user_logged_in() ) { return '
' . Ui::notice( esc_html( ! empty( $s['login_msg'] ) ? $s['login_msg'] : __( 'Please log in to submit.', 'frontglut' ) ), 'warn' ) . \FrontGlut\Modules\Auth\Auth::login_form( array( 'redirect' => get_permalink() ) ) . '
'; } if ( is_user_logged_in() && empty( $s['guest'] ) && ! apply_filters( 'frontglut_can_submit', Caps::can( 'frontglut_manage_posts' ), isset( $s['post_type'] ) ? $s['post_type'] : 'post', true, $form ) ) { return '
' . Ui::notice( esc_html__( 'Your account may not submit posts.', 'frontglut' ), 'warn' ) . '
'; } return '
' . $this->form_html( $form ) . '
'; } public function sc_edit( $atts ) { $id = Request::route( 'frontglut_edit', 'int', isset( $atts['id'] ) ? (int) $atts['id'] : 0 ); Assets::need(); if ( ! $id || ! self::can_edit( $id ) ) { return '
' . Ui::notice( esc_html__( 'You cannot edit this post.', 'frontglut' ), 'warn' ) . '
'; } $post = get_post( $id ); $form = $this->form_for_post( $post ); return '
' . $this->form_html( $form, $post ) . '
'; } public function sc_list( $atts ) { Assets::need(); if ( ! is_user_logged_in() ) { return '
' . Ui::notice( esc_html__( 'Please log in.', 'frontglut' ), 'warn' ) . '
'; } if ( Request::route( 'frontglut_edit', 'bool', false ) ) { return $this->sc_edit( array() ); } return '
' . $this->list_html( 'post', get_permalink(), 'frontglut_edit' ) . '
'; } private function form_for_post( \WP_Post $post ) { $fid = (int) get_post_meta( $post->ID, '_frontglut_form', true ); $form = $fid ? Forms::get( $fid ) : null; if ( ! $form ) { foreach ( Forms::all( 'post' ) as $f ) { if ( ( isset( $f['settings']['post_type'] ) ? $f['settings']['post_type'] : 'post' ) === $post->post_type ) { $form = $f; break; } } } return $form ? $form : Forms::default_form( 'post_member' ); } /* ---------------- dashboard screens ---------------- */ /** * Generic list/new/edit screen for a post type. Also used by the post-type builder. */ public function manage_screen( $section, $post_type, $label, $form_id ) { $a = Request::route( 'a', 'key', 'list' ); $id = Request::route( 'id', 'int', 0 ); if ( 'new' === $a ) { $form = $form_id ? Forms::get( $form_id ) : $this->first_form_for( $post_type ); if ( ! $form ) { return Ui::notice( esc_html__( 'No form is configured for this content type. Create one under FrontGlut → Forms.', 'frontglut' ), 'warn' ); } /* translators: %s: value. */ return Ui::header( sprintf( __( 'New: %s', 'frontglut' ), $label ), Ui::btn( __( '← Back', 'frontglut' ), Dashboard::url( $section ) ) ) . '
' . $this->form_html( $form ) . '
'; } if ( 'edit' === $a ) { if ( ! $id || ! self::can_edit( $id ) || get_post_type( $id ) !== $post_type ) { return Ui::notice( esc_html__( 'You cannot edit this item.', 'frontglut' ), 'warn' ); } $post = get_post( $id ); $form = $this->form_for_post( $post ); /* translators: %s: value. */ return Ui::header( sprintf( __( 'Edit: %s', 'frontglut' ), $post->post_title ), Ui::btn( __( '← Back', 'frontglut' ), Dashboard::url( $section ) ) . ' ' . Ui::btn( __( 'Preview', 'frontglut' ), Dashboard::url( $section, array( 'a' => 'preview', 'id' => $id ) ) ) ) . '
' . $this->form_html( $form, $post ) . '
'; } if ( 'preview' === $a ) { $p = get_post( $id ); if ( ! $p || ! self::can_edit( $id ) ) { return Ui::notice( esc_html__( 'Not available.', 'frontglut' ), 'warn' ); } return Ui::header( __( 'Preview', 'frontglut' ), Ui::btn( __( 'Edit', 'frontglut' ), Dashboard::url( $section, array( 'a' => 'edit', 'id' => $id ) ) ) ) . '

' . esc_html( $p->post_title ) . '

' . apply_filters( 'the_content', $p->post_content ) . '
'; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals } $newbtn = Ui::btn( __( 'New', 'frontglut' ), Dashboard::url( $section, array( 'a' => 'new' ) ), 'frontglut-primary' ); return Ui::header( $label, $newbtn ) . $this->list_html( $post_type, Dashboard::url( $section ), '', $section ); } private function first_form_for( $post_type ) { foreach ( Forms::all( 'post' ) as $f ) { if ( ( isset( $f['settings']['post_type'] ) ? $f['settings']['post_type'] : 'post' ) === $post_type && empty( $f['settings']['guest'] ) ) { return $f; } } return null; } /** List of the current user's (or everyone's if moderator toggled) posts. */ public function list_html( $post_type, $base_url, $edit_param = '', $section = 'posts' ) { $uid = get_current_user_id(); $status = Request::filter( 'status', 'key', '' ); $q = Request::filter( 's', 'text', '' ); $pg = Request::page(); $all = Request::filter( 'all', 'bool', false ) && Caps::can( 'frontglut_moderate' ); $args = array( 'post_type' => $post_type, 'posts_per_page' => 15, 'paged' => $pg, 'post_status' => $status ? $status : array( 'publish', 'pending', 'draft', 'private' ), 's' => $q, 'orderby' => 'date', 'order' => 'DESC' ); if ( ! $all ) { $args['author'] = $uid; } $query = new \WP_Query( $args ); $c = $this->counts( $uid, $post_type ); $pills = '
'; foreach ( array( '' => __( 'All', 'frontglut' ), 'publish' => __( 'Published', 'frontglut' ), 'pending' => __( 'Pending', 'frontglut' ), 'draft' => __( 'Draft', 'frontglut' ), 'trash' => __( 'Trash', 'frontglut' ) ) as $k => $l ) { $n = '' === $k ? array_sum( array_diff_key( $c, array( 'trash' => 1 ) ) ) : $c[ $k ]; $pills .= '' . esc_html( $l ) . ' (' . (int) $n . ')'; } $pills .= '
'; $out = $pills; if ( Caps::can( 'frontglut_moderate' ) ) { $out .= '

' . esc_html( $all ? __( 'Show only mine', 'frontglut' ) : __( 'Show everyone\'s', 'frontglut' ) ) . '

'; } $out .= '
' . Request::fields() . '
'; // phpcs:ignore WordPress.Security.NonceVerification $rows = array(); foreach ( $query->posts as $p ) { $edit = $edit_param ? add_query_arg( $edit_param, $p->ID, $base_url ) : Dashboard::url( $section, array( 'a' => 'edit', 'id' => $p->ID ) ); $acts = ''; if ( 'trash' === $p->post_status ) { $acts .= Ui::action_btn( __( 'Restore', 'frontglut' ), 'post_restore', array( 'id' => $p->ID ) ) . Ui::action_btn( __( 'Delete forever', 'frontglut' ), 'post_delete_forever', array( 'id' => $p->ID ), 'frontglut-danger', true ); } else { if ( self::can_edit( $p->ID ) ) { $acts .= '' . esc_html__( 'Edit', 'frontglut' ) . ''; } if ( 'publish' === $p->post_status ) { $acts .= '' . esc_html__( 'View', 'frontglut' ) . ''; } $acts .= Ui::kses( apply_filters( 'frontglut_post_row_actions', '', $p ) ); if ( $this->can_delete( $p->ID ) ) { $acts .= Ui::action_btn( __( 'Delete', 'frontglut' ), 'post_trash', array( 'id' => $p->ID ), 'frontglut-danger', true ); } } $reason = get_post_meta( $p->ID, '_frontglut_reject_reason', true ); $rows[] = array( '_id' => $p->ID, /* translators: %s: value. */ 'title' => '' . esc_html( get_the_title( $p ) ?: __( '(no title)', 'frontglut' ) ) . '' . ( $reason && 'draft' === $p->post_status ? '
' . esc_html( sprintf( __( 'Rejected: %s', 'frontglut' ), $reason ) ) . '' : '' ), 'status' => Ui::status_badge( $p->post_status ), 'date' => Ui::date( $p->post_date_gmt && '0000-00-00 00:00:00' !== $p->post_date_gmt ? $p->post_date_gmt : $p->post_date ), 'views' => (int) get_post_meta( $p->ID, 'frontglut_views', true ), 'act' => '
' . $acts . '
', ); } $cols = array( 'title' => __( 'Title', 'frontglut' ), 'status' => __( 'Status', 'frontglut' ), 'date' => __( 'Date', 'frontglut' ) ); if ( Settings::get( 'posts.show_view_count' ) ) { $cols['views'] = __( 'Views', 'frontglut' ); } $cols['act'] = ''; $bulk = '
' . Ui::action_btn( __( 'Trash selected', 'frontglut' ), 'posts_bulk', array( 'op' => 'trash' ), 'frontglut-danger', true ) . '
'; $out .= ( $rows ? $bulk : '' ) . Ui::table( $cols, $rows, array( 'bulk' => true, 'empty' => __( 'No posts found.', 'frontglut' ) ) ) . Ui::pager( $query->found_posts, 15, $pg, array( 'status' => $status, 's' => $q ) ); return $out; } /* ---------------- submission ---------------- */ private function resolve_status( array $form, $user_id ) { $s = $form['settings']; $base = isset( $s['status'] ) ? $s['status'] : 'pending'; if ( $user_id ) { $u = get_userdata( $user_id ); foreach ( preg_split( '/\r\n|\r|\n/', (string) ( isset( $s['status_roles'] ) ? $s['status_roles'] : '' ) ) as $l ) { $p = array_map( 'trim', explode( '|', $l ) ); if ( 2 === count( $p ) && in_array( $p[0], (array) $u->roles, true ) && in_array( $p[1], array( 'publish', 'pending', 'draft' ), true ) ) { return $p[1]; } } if ( Caps::can( 'frontglut_publish_posts', $user_id ) ) { return 'publish'; } } return $base; } public function handle_form( $pre, array $form, array $req ) { $s = $form['settings']; $post_id = isset( $req['post_id'] ) ? (int) $req['post_id'] : 0; $logged = is_user_logged_in(); $uid = get_current_user_id(); $edit = $post_id > 0; $ptype = isset( $s['post_type'] ) && post_type_exists( $s['post_type'] ) ? $s['post_type'] : 'post'; if ( $edit ) { if ( ! $logged || ! self::can_edit( $post_id ) || get_post_type( $post_id ) !== $ptype ) { return new \WP_Error( 'perm', __( 'You cannot edit this post.', 'frontglut' ) ); } } elseif ( ! apply_filters( 'frontglut_can_submit', ! ( $logged && empty( $s['guest'] ) && ! Caps::can( 'frontglut_manage_posts' ) ), $ptype, $logged, $form ) ) { return new \WP_Error( 'perm', __( 'Your account may not submit posts.', 'frontglut' ) ); } $gate = apply_filters( 'frontglut_post_pre_submit', null, $form, $req, $edit ); if ( is_wp_error( $gate ) ) { return $gate; } $guest_name = $guest_email = ''; $errors = array(); if ( ! $logged ) { $guest_name = isset( $req['guest_name'] ) ? sanitize_text_field( $req['guest_name'] ) : ''; $guest_email = isset( $req['guest_email'] ) ? sanitize_email( $req['guest_email'] ) : ''; if ( '' === $guest_name ) { $errors['guest_name'] = __( 'Your name is required.', 'frontglut' ); } if ( ! is_email( $guest_email ) ) { $errors['guest_email'] = __( 'A valid email is required.', 'frontglut' ); } } $post = isset( $req['frontglut_f'] ) && is_array( $req['frontglut_f'] ) ? $req['frontglut_f'] : array(); list( $v, $ferr ) = Fields::process( $form['fields'], $post ); $errors = array_merge( $errors, $ferr ); if ( $errors ) { return new \WP_Error( 'invalid', __( 'Please fix the highlighted fields.', 'frontglut' ), $errors ); } $arr = array( 'post_type' => $ptype ); $meta = array(); $terms = array(); $featured = 0; $galleries = array(); foreach ( $form['fields'] as $f ) { $k = $f['key']; if ( ! array_key_exists( $k, $v ) ) { continue; } $val = $v[ $k ]; switch ( $f['storage'] ) { case 'post_title': $arr['post_title'] = (string) $val; break; case 'post_content': $arr['post_content'] = (string) $val; break; case 'post_excerpt': $arr['post_excerpt'] = (string) $val; break; case 'featured': $ids = Uploads::filter_owned( (array) $val ); $featured = $ids ? (int) $ids[0] : 0; break; case 'gallery': $galleries[ $k ] = Uploads::filter_owned( (array) $val ); break; case 'tax': $tax = isset( $f['taxonomy'] ) ? $f['taxonomy'] : 'category'; if ( taxonomy_exists( $tax ) && is_object_in_taxonomy( $ptype, $tax ) ) { $terms[ $tax ] = array( 'val' => $val, 'ui' => isset( $f['ui'] ) ? $f['ui'] : 'select' ); } break; default: if ( in_array( $f['type'], array( 'file', 'image' ), true ) ) { $ids = Uploads::filter_owned( (array) $val ); $meta[ $k ] = ! empty( $f['multiple'] ) ? $ids : ( $ids ? $ids[0] : '' ); $galleries[ '__f' . $k ] = $ids; } else { $meta[ $k ] = $val; } } } if ( empty( $arr['post_title'] ) && ! $edit ) { $arr['post_title'] = wp_trim_words( wp_strip_all_tags( isset( $arr['post_content'] ) ? $arr['post_content'] : '' ), 8, '…' ); } if ( isset( $arr['post_title'] ) && '' === trim( $arr['post_title'] ) ) { return new \WP_Error( 'invalid', __( 'A title is required.', 'frontglut' ), array() ); } if ( ! empty( $arr['post_content'] ) && ! Caps::can( 'unfiltered_html' ) ) { $arr['post_content'] = wp_kses_post( $arr['post_content'] ); } if ( Settings::get( 'posts.dup_guard' ) && ! $edit && ! empty( $arr['post_title'] ) ) { global $wpdb; $dup = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_title = %s AND post_author = %d AND post_status IN ('publish','pending','draft') AND post_date > %s LIMIT 1", $ptype, $arr['post_title'], $logged ? $uid : $this->guest_author( $s ), wp_date( 'Y-m-d H:i:s', time() - DAY_IN_SECONDS ) ) ); if ( $dup ) { return new \WP_Error( 'dup', __( 'You already submitted something with this title.', 'frontglut' ) ); } } if ( $edit ) { $arr['ID'] = $post_id; $old = get_post( $post_id ); $status = $old->post_status; if ( 'publish' === $status && Settings::get( 'posts.reedit_pending' ) && ! Caps::can( 'frontglut_moderate' ) && ! Caps::can( 'frontglut_publish_posts' ) ) { $status = 'pending'; } $arr['post_status'] = $status; $id = wp_update_post( wp_slash( $arr ), true ); } else { $arr['post_status'] = apply_filters( 'frontglut_post_status', $this->resolve_status( $form, $uid ), $form, $uid, $ptype ); $arr['post_author'] = $logged ? $uid : $this->guest_author( $s ); $id = wp_insert_post( wp_slash( $arr ), true ); } if ( is_wp_error( $id ) ) { return $id; } foreach ( $meta as $k => $val ) { if ( is_protected_meta( $k, 'post' ) || 0 === strpos( $k, 'frontglut_' ) ) { continue; // forms may not write protected / plugin-internal meta. } if ( '' === $val || array() === $val ) { delete_post_meta( $id, $k ); } else { update_post_meta( $id, $k, $val ); } } foreach ( $galleries as $gk => $ids ) { if ( 0 === strpos( $gk, '__f' ) ) { Uploads::claim( $ids, $id ); continue; } update_post_meta( $id, '_frontglut_' . $gk, $ids ); Uploads::claim( $ids, $id ); } if ( $featured ) { set_post_thumbnail( $id, $featured ); Uploads::claim( array( $featured ), $id ); } elseif ( $edit && $this->has_storage( $form, 'featured' ) ) { delete_post_thumbnail( $id ); } foreach ( $terms as $tax => $t ) { $list = (array) $t['val']; if ( 'tags' === $t['ui'] ) { if ( ! Caps::can( 'frontglut_manage_posts' ) && ! $logged ) { $list = array_values( array_filter( $list, function ( $n ) use ( $tax ) { return term_exists( $n, $tax ); } ) ); } wp_set_object_terms( $id, $list, $tax ); } else { wp_set_object_terms( $id, array_map( 'intval', $list ), $tax ); } } if ( ! $edit ) { update_post_meta( $id, '_frontglut_form', $form['id'] ); if ( ! $logged ) { update_post_meta( $id, '_frontglut_ip', Audit::stored_ip() ); update_post_meta( $id, '_frontglut_guest_name', $guest_name ); update_post_meta( $id, '_frontglut_guest_email', $guest_email ); } } update_post_meta( $id, '_frontglut_last_edit', time() ); delete_post_meta( $id, '_frontglut_reject_reason' ); $final = get_post( $id ); $this->notify_submit( $final, $form, $edit, $guest_name, $guest_email ); do_action( 'frontglut_post_submitted', $id, $form, $edit ); Audit::log( $edit ? 'post_updated' : 'post_submitted', (string) $id ); $redirect = ''; if ( ! empty( $s['redirect'] ) ) { $redirect = esc_url_raw( $s['redirect'] ); } elseif ( $logged && Dashboard::is_dashboard_request() ) { $redirect = Dashboard::url( $this->section_for_type( $ptype ) ); } elseif ( $logged && $edit ) { $redirect = remove_query_arg( 'frontglut_edit', wp_get_referer() ? wp_get_referer() : home_url( '/' ) ); } if ( ! $redirect && $logged && ! $edit && wp_get_referer() && false !== strpos( wp_get_referer(), (string) Settings::get( 'general.dashboard_page' ) ) ) { $redirect = Dashboard::url( $this->section_for_type( $ptype ) ); } $msg = ! empty( $s['success'] ) ? $s['success'] : __( 'Saved.', 'frontglut' ); if ( $edit ) { $msg = __( 'Post updated.', 'frontglut' ); } return apply_filters( 'frontglut_post_submit_result', array( 'message' => $msg, 'redirect' => $redirect, 'id' => $id, 'status' => $final->post_status ), $id, $form, $edit ); } private function has_storage( array $form, $storage ) { foreach ( $form['fields'] as $f ) { if ( $f['storage'] === $storage ) { return true; } } return false; } private function section_for_type( $ptype ) { return apply_filters( 'frontglut_section_for_post_type', 'post' === $ptype ? 'posts' : 'cpt_' . $ptype, $ptype ); } private function guest_author( array $s ) { $a = isset( $s['author'] ) ? (int) $s['author'] : 0; if ( $a && get_userdata( $a ) ) { return $a; } $admins = get_users( array( 'role' => 'administrator', 'number' => 1, 'fields' => 'ID', 'orderby' => 'ID' ) ); return $admins ? (int) $admins[0] : 1; } private function notify_submit( \WP_Post $p, array $form, $edit, $guest_name, $guest_email ) { $s = $form['settings']; $author = $guest_name ? $guest_name : get_the_author_meta( 'display_name', $p->post_author ); $vars = array( 'post_title' => $p->post_title, 'author' => $author, 'status' => $p->post_status, 'review_url' => Dashboard::url( 'moderation' ), 'post_url' => get_permalink( $p ), 'edit_url' => Dashboard::url( 'posts', array( 'a' => 'edit', 'id' => $p->ID ) ) ); if ( ! $edit && ! empty( $s['notify_admin'] ) ) { Mailer::send_template( 'admin_new_post', get_option( 'admin_email' ), $vars ); } if ( ! $edit ) { /* translators: %s: value. */ Notify::send_to_cap( 'frontglut_moderate', sprintf( __( 'New submission: %s', 'frontglut' ), $p->post_title ), sprintf( __( 'By %s', 'frontglut' ), $author ), Dashboard::url( 'moderation' ), (int) $p->post_author ); } if ( ! $edit && ! empty( $s['notify_user'] ) ) { $to = $guest_email ? $guest_email : get_the_author_meta( 'user_email', $p->post_author ); Mailer::send_template( 'post_received', $to, $vars ); } } /* ---------------- list actions ---------------- */ public function ajax_trash( $req ) { $id = (int) ( $req['id'] ?? 0 ); if ( ! $this->can_delete( $id ) ) { Ajax::fail( __( 'Not allowed.', 'frontglut' ), 403 ); } Settings::get( 'posts.soft_delete' ) ? wp_trash_post( $id ) : wp_delete_post( $id, true ); Audit::log( 'post_trashed', (string) $id ); return array( 'message' => __( 'Deleted.', 'frontglut' ) ); } public function ajax_restore( $req ) { $id = (int) ( $req['id'] ?? 0 ); if ( ! $this->can_delete( $id ) ) { Ajax::fail( __( 'Not allowed.', 'frontglut' ), 403 ); } wp_untrash_post( $id ); return array( 'message' => __( 'Restored.', 'frontglut' ) ); } public function ajax_delete( $req ) { $id = (int) ( $req['id'] ?? 0 ); if ( ! $this->can_delete( $id ) || 'trash' !== get_post_status( $id ) ) { Ajax::fail( __( 'Not allowed.', 'frontglut' ), 403 ); } wp_delete_post( $id, true ); return array( 'message' => __( 'Deleted permanently.', 'frontglut' ) ); } public function ajax_bulk( $req ) { $ids = array_filter( array_map( 'absint', explode( ',', (string) ( $req['ids'] ?? '' ) ) ) ); $n = 0; foreach ( $ids as $id ) { if ( $this->can_delete( $id ) && 'trash' !== get_post_status( $id ) ) { wp_trash_post( $id ); $n++; } } /* translators: %s: value. */ return array( 'message' => sprintf( _n( '%d post moved to trash.', '%d posts moved to trash.', $n, 'frontglut' ), $n ) ); } /* ---------------- moderation ---------------- */ public function render_moderation() { $pg = Request::page(); $q = new \WP_Query( array( 'post_type' => $this->managed_types(), 'post_status' => 'pending', 'posts_per_page' => 15, 'paged' => $pg, 'orderby' => 'date', 'order' => 'ASC' ) ); $rows = array(); foreach ( $q->posts as $p ) { $guest = get_post_meta( $p->ID, '_frontglut_guest_name', true ); $rows[] = array( 'title' => '' . esc_html( $p->post_title ) . '
' . esc_html( get_post_type_object( $p->post_type )->labels->singular_name ) . '', 'author' => esc_html( $guest ? $guest . ' (guest)' : get_the_author_meta( 'display_name', $p->post_author ) ), 'date' => Ui::date( $p->post_date ), 'act' => '
' . Ui::action_btn( __( 'Approve', 'frontglut' ), 'post_approve', array( 'id' => $p->ID ), 'frontglut-primary' ) . '
', ); } if ( 'review' === Request::route( 'a' ) && Request::route( 'id', 'int', 0 ) ) { $p = get_post( (int) Request::route( 'id', 'int', 0 ) ); if ( $p ) { $bar = Ui::btn( __( '← Back', 'frontglut' ), Dashboard::url( 'moderation' ) ) . ' ' . Ui::btn( __( 'Edit', 'frontglut' ), Dashboard::url( 'posts', array( 'a' => 'edit', 'id' => $p->ID ) ) ); if ( 'pending' === $p->post_status ) { $bar .= ' ' . Ui::action_btn( __( 'Approve', 'frontglut' ), 'post_approve', array( 'id' => $p->ID ), 'frontglut-primary' ); } return Ui::header( $p->post_title, $bar ) . '
' . apply_filters( 'the_content', $p->post_content ) . '
'; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals } } return Ui::header( __( 'Moderation queue', 'frontglut' ), '', __( 'Submissions waiting for approval.', 'frontglut' ) ) . Ui::table( array( 'title' => __( 'Submission', 'frontglut' ), 'author' => __( 'Author', 'frontglut' ), 'date' => __( 'Submitted', 'frontglut' ), 'act' => '' ), $rows, array( 'empty' => __( 'Nothing is waiting for review. 🎉', 'frontglut' ) ) ) . Ui::pager( $q->found_posts, 15, $pg ); } private function submitter_email( \WP_Post $p ) { $g = get_post_meta( $p->ID, '_frontglut_guest_email', true ); return $g ? $g : get_the_author_meta( 'user_email', $p->post_author ); } public function ajax_approve( $req ) { $p = get_post( (int) ( $req['id'] ?? 0 ) ); if ( ! $p || ! in_array( $p->post_type, $this->managed_types(), true ) ) { Ajax::fail( __( 'Not found.', 'frontglut' ), 404 ); } $gate = apply_filters( 'frontglut_pre_approve', null, $p ); if ( is_wp_error( $gate ) ) { Ajax::fail( $gate->get_error_message() ); } wp_update_post( array( 'ID' => $p->ID, 'post_status' => 'publish' ) ); do_action( 'frontglut_post_approved', $p->ID ); delete_post_meta( $p->ID, '_frontglut_reject_reason' ); $name = get_post_meta( $p->ID, '_frontglut_guest_name', true ); Mailer::send_template( 'post_approved', $this->submitter_email( $p ), array( 'post_title' => $p->post_title, 'author' => $name ? $name : get_the_author_meta( 'display_name', $p->post_author ), 'post_url' => get_permalink( $p->ID ) ) ); /* translators: %s: value. */ Notify::send( $p->post_author, sprintf( __( '"%s" was approved', 'frontglut' ), $p->post_title ), '', get_permalink( $p->ID ), 'ok' ); Audit::log( 'post_approved', (string) $p->ID ); return array( 'message' => __( 'Approved and published.', 'frontglut' ) ); } public function ajax_reject( $req ) { $p = get_post( (int) ( $req['id'] ?? 0 ) ); if ( ! $p || ! in_array( $p->post_type, $this->managed_types(), true ) ) { Ajax::fail( __( 'Not found.', 'frontglut' ), 404 ); } $reason = sanitize_textarea_field( $req['reason'] ?? '' ); wp_update_post( array( 'ID' => $p->ID, 'post_status' => 'draft' ) ); do_action( 'frontglut_post_rejected', $p->ID, $reason ); update_post_meta( $p->ID, '_frontglut_reject_reason', $reason ); $name = get_post_meta( $p->ID, '_frontglut_guest_name', true ); Mailer::send_template( 'post_rejected', $this->submitter_email( $p ), array( 'post_title' => $p->post_title, 'author' => $name ? $name : get_the_author_meta( 'display_name', $p->post_author ), 'reason' => $reason ? $reason : '—', 'edit_url' => Dashboard::url( 'posts', array( 'a' => 'edit', 'id' => $p->ID ) ) ) ); /* translators: %s: value. */ Notify::send( $p->post_author, sprintf( __( '"%s" needs changes', 'frontglut' ), $p->post_title ), $reason, Dashboard::url( 'posts', array( 'a' => 'edit', 'id' => $p->ID ) ), 'warn' ); Audit::log( 'post_rejected', (string) $p->ID, $reason ); return array( 'message' => __( 'Rejected and author notified.', 'frontglut' ) ); } /* ---------------- terms manager ---------------- */ public function render_terms() { $taxes = array(); foreach ( get_taxonomies( array( 'public' => true, 'show_ui' => true ), 'objects' ) as $t ) { if ( ! in_array( $t->name, array( 'post_format', 'product_shipping_class' ), true ) ) { $taxes[ $t->name ] = $t; } } $cur = isset( $taxes[ Request::route( 'tax' ) ] ) ? Request::route( 'tax' ) : 'category'; if ( ! isset( $taxes[ $cur ] ) ) { $cur = key( $taxes ); } $pills = '
'; foreach ( $taxes as $k => $t ) { $pills .= '' . esc_html( $t->labels->name ) . ''; } $pills .= '
'; $terms = get_terms( array( 'taxonomy' => $cur, 'hide_empty' => false, 'number' => 300 ) ); $rows = array(); foreach ( (array) $terms as $t ) { if ( is_wp_error( $t ) ) { continue; } $rows[] = array( 'name' => '
' . Ui::hidden( 'id', $t->term_id ) . Ui::hidden( 'taxonomy', $cur ) . '
', 'slug' => esc_html( $t->slug ), 'count' => (int) $t->count, 'act' => Ui::action_btn( __( 'Delete', 'frontglut' ), 'term_delete', array( 'id' => $t->term_id, 'taxonomy' => $cur ), 'frontglut-danger', true ), ); } $hier = is_taxonomy_hierarchical( $cur ) ? '
' . wp_dropdown_categories( array( 'taxonomy' => $cur, 'name' => 'parent', 'show_option_none' => __( 'None', 'frontglut' ), 'option_none_value' => 0, 'hide_empty' => 0, 'echo' => 0, 'class' => 'frontglut-input' ) ) . '
' : ''; $add = '

' . esc_html__( 'Add new', 'frontglut' ) . '

' . Ui::form_open( 'term_save', array( 'after' => 'reload' ) ) . Ui::hidden( 'taxonomy', $cur ) . '
' . $hier . Ui::form_close( __( 'Add', 'frontglut' ) ) . '
'; return Ui::header( __( 'Categories & tags', 'frontglut' ) ) . $pills . $add . Ui::table( array( 'name' => __( 'Name', 'frontglut' ), 'slug' => __( 'Slug', 'frontglut' ), 'count' => __( 'Count', 'frontglut' ), 'act' => '' ), $rows ); } private function tax_ok( $tax ) { return $tax && taxonomy_exists( $tax ) && get_taxonomy( $tax )->public && ! in_array( $tax, array( 'post_format' ), true ); } public function ajax_term_save( $req ) { $tax = sanitize_key( $req['taxonomy'] ?? '' ); $name = sanitize_text_field( $req['name'] ?? '' ); if ( ! $this->tax_ok( $tax ) || '' === $name ) { Ajax::fail( __( 'Invalid request.', 'frontglut' ) ); } $id = (int) ( $req['id'] ?? 0 ); $r = $id ? wp_update_term( $id, $tax, array( 'name' => $name ) ) : wp_insert_term( $name, $tax, array( 'parent' => (int) ( $req['parent'] ?? 0 ) ) ); if ( is_wp_error( $r ) ) { Ajax::fail( $r->get_error_message() ); } return array( 'message' => __( 'Saved.', 'frontglut' ) ); } public function ajax_term_delete( $req ) { $tax = sanitize_key( $req['taxonomy'] ?? '' ); if ( ! $this->tax_ok( $tax ) ) { Ajax::fail( __( 'Invalid request.', 'frontglut' ) ); } $r = wp_delete_term( (int) ( $req['id'] ?? 0 ), $tax ); if ( is_wp_error( $r ) || ! $r ) { Ajax::fail( __( 'Could not delete (default term?).', 'frontglut' ) ); } return array( 'message' => __( 'Deleted.', 'frontglut' ) ); } /* ---------------- views ---------------- */ public function count_view() { if ( ! is_singular() || ! Settings::get( 'posts.show_view_count' ) || is_user_logged_in() && current_user_can( 'edit_post', get_the_ID() ) ) { return; } $id = get_queried_object_id(); if ( $id && get_post_meta( $id, '_frontglut_form', true ) && empty( $_SERVER['HTTP_X_PURPOSE'] ) ) { $ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) ) : ''; // Only checked for bot names; never stored. if ( $ua && ! preg_match( '/bot|crawl|spider|slurp|preview/', $ua ) ) { update_post_meta( $id, 'frontglut_views', (int) get_post_meta( $id, 'frontglut_views', true ) + 1 ); } } } }