'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 .= ''; // 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 = '';
$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' => '',
'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 );
}
}
}
}