From 9c08692d5d2abe7e7198d940a11bd12001568c52 Mon Sep 17 00:00:00 2001
From: Corey McKrill <916023+coreymckrill@users.noreply.github.com>
Date: Fri, 18 Oct 2019 16:56:21 -0700
Subject: [PATCH] New Pledge: Validate submitted form values and generate a
draft pledge post (#35)
Ensures that a submission to the new pledge form has:
* A unique email address compared to existing pledges
* A unique domain in the URL, compared to existing pledges
* Has at least one valid contributor listed
Error messages when one or more of these conditions isn't met are descriptive so that the submitter can correct the issue.
Fixes #15
---
plugins/wporg-5ftf/includes/pledge-form.php | 191 ++++++++++++++++--
plugins/wporg-5ftf/includes/pledge-meta.php | 57 +++---
.../views/inputs-pledge-contributors.php | 2 +-
.../views/inputs-pledge-new-misc.php | 7 +-
4 files changed, 202 insertions(+), 55 deletions(-)
diff --git a/plugins/wporg-5ftf/includes/pledge-form.php b/plugins/wporg-5ftf/includes/pledge-form.php
index 0c69a17..263bf04 100755
--- a/plugins/wporg-5ftf/includes/pledge-form.php
+++ b/plugins/wporg-5ftf/includes/pledge-form.php
@@ -8,7 +8,7 @@ namespace WordPressDotOrg\FiveForTheFuture\PledgeForm;
use WordPressDotOrg\FiveForTheFuture;
use WordPressDotOrg\FiveForTheFuture\Pledge;
use WordPressDotOrg\FiveForTheFuture\PledgeMeta;
-use WP_Error;
+use WP_Error, WP_Post, WP_User;
defined( 'WPINC' ) || die();
@@ -23,9 +23,9 @@ add_shortcode( '5ftf_pledge_form_manage', __NAMESPACE__ . '\render_form_manage'
*/
function render_form_new() {
$action = filter_input( INPUT_POST, 'action' );
+ $data = get_form_submission();
$messages = [];
$complete = false;
- $data = PledgeMeta\get_pledge_meta();
if ( 'Submit Pledge' === $action ) {
$processed = process_form_new();
@@ -45,12 +45,12 @@ function render_form_new() {
}
/**
- *
+ * Process a submission from the New Pledge form.
*
* @return string|WP_Error String "success" if the form processed correctly. Otherwise WP_Error.
*/
function process_form_new() {
- $submission = filter_input_array( INPUT_POST, PledgeMeta\get_input_filters() );
+ $submission = get_form_submission();
$has_required = PledgeMeta\has_required_pledge_meta( $submission );
@@ -58,15 +58,35 @@ function process_form_new() {
return $has_required;
}
+ $email = sanitize_meta(
+ PledgeMeta\META_PREFIX . 'org-pledge-email',
+ $submission['org-pledge-email'],
+ 'post',
+ Pledge\CPT_ID
+ );
+
+ if ( has_existing_pledge( $email, 'email' ) ) {
+ return new WP_Error(
+ 'existing_pledge_email',
+ __( 'This email address is already connected to an existing pledge.', 'wporg' )
+ );
+ }
+
$domain = PledgeMeta\get_normalized_domain_from_url( $submission['org-url'] );
- if ( has_existing_pledge( $domain ) ) {
+ if ( has_existing_pledge( $domain, 'domain' ) ) {
return new WP_Error(
- 'existing_pledge',
+ 'existing_pledge_domain',
__( 'A pledge already exists for this domain.', 'wporg' )
);
}
+ $contributors = parse_contributors( $submission['pledge-contributors'] );
+
+ if ( is_wp_error( $contributors ) ) {
+ return $contributors;
+ }
+
$name = sanitize_meta(
PledgeMeta\META_PREFIX . 'org-name',
$submission['org-name'],
@@ -80,8 +100,6 @@ function process_form_new() {
return $created;
}
- PledgeMeta\save_pledge_meta( $created, $submission );
-
return 'success';
}
@@ -116,12 +134,12 @@ function render_form_manage() {
}
/**
- *
+ * Process a submission from the Manage Existing Pledge form.
*
* @return string|WP_Error String "success" if the form processed correctly. Otherwise WP_Error.
*/
function process_form_manage() {
- $submission = filter_input_array( INPUT_POST, PledgeMeta\get_input_filters() );
+ $submission = get_form_submission();
$has_required = PledgeMeta\has_required_pledge_meta( $submission );
@@ -129,9 +147,23 @@ function process_form_manage() {
return $has_required;
}
+ $email = sanitize_meta(
+ PledgeMeta\META_PREFIX . 'org-pledge-email',
+ $submission['org-pledge-email'],
+ 'post',
+ Pledge\CPT_ID
+ );
+
+ if ( has_existing_pledge( $email, 'email' ) ) {
+ return new WP_Error(
+ 'existing_pledge_email',
+ __( 'This email address is already connected to an existing pledge.', 'wporg' )
+ );
+ }
+
$domain = PledgeMeta\get_normalized_domain_from_url( $submission['org-url'] );
- if ( has_existing_pledge( $domain ) ) {
+ if ( has_existing_pledge( $domain, 'domain' ) ) {
return new WP_Error(
'existing_pledge',
__( 'A pledge already exists for this domain.', 'wporg' )
@@ -140,25 +172,58 @@ function process_form_manage() {
}
/**
+ * Get and sanitize $_POST values from a form submission.
*
+ * @return array|bool
+ */
+function get_form_submission() {
+ $input_filters = array_merge(
+ // Inputs that correspond to meta values.
+ wp_list_pluck( PledgeMeta\get_pledge_meta_config( 'user_input' ), 'php_filter' ),
+ // Inputs with no corresponding meta value.
+ array(
+ 'pledge-contributors' => FILTER_SANITIZE_STRING,
+ 'pledge-agreement' => FILTER_VALIDATE_BOOLEAN,
+ )
+ );
+
+ return filter_input_array( INPUT_POST, $input_filters );
+}
+
+/**
+ * Check a key value against existing pledges to see if one already exists.
*
- * @param string $domain
- * @param int $current_pledge_id
+ * @param string $key The value to match against other pledges.
+ * @param string $key_type The type of value being matched. `email` or `domain`.
+ * @param int $current_pledge_id Optional. The post ID of the pledge to compare against others.
*
* @return bool
*/
-function has_existing_pledge( $domain, int $current_pledge_id = 0 ) {
+function has_existing_pledge( $key, $key_type, int $current_pledge_id = 0 ) {
$args = array(
'post_type' => Pledge\CPT_ID,
- 'post_status' => array( 'pending', 'publish' ),
- 'meta_query' => array(
- array(
- 'key' => PledgeMeta\META_PREFIX . 'org-domain',
- 'value' => $domain,
- ),
- ),
+ 'post_status' => array( 'draft', 'pending', 'publish' ),
);
+ switch ( $key_type ) {
+ case 'email':
+ $args['meta_query'] = array(
+ array(
+ 'key' => PledgeMeta\META_PREFIX . 'org-pledge-email',
+ 'value' => $key,
+ ),
+ );
+ break;
+ case 'domain':
+ $args['meta_query'] = array(
+ array(
+ 'key' => PledgeMeta\META_PREFIX . 'org-domain',
+ 'value' => $key,
+ ),
+ );
+ break;
+ }
+
if ( $current_pledge_id ) {
$args['exclude'] = array( $current_pledge_id );
}
@@ -168,6 +233,90 @@ function has_existing_pledge( $domain, int $current_pledge_id = 0 ) {
return ! empty( $matching_pledge );
}
+/**
+ * TODO Move this to the contributor cpt include file.
+ *
+ * @param int $pledge_id
+ *
+ * @return array
+ */
+function get_pledge_contributors( $pledge_id = 0 ) {
+ $contributors = array();
+
+ // Get POST'd submission, if it exists.
+ $submission = filter_input( INPUT_POST, 'pledge-contributors', FILTER_SANITIZE_STRING );
+
+ // Get existing pledge, if it exists.
+ $pledge = get_post( $pledge_id );
+
+ if ( ! empty( $submission ) ) {
+ $contributors = array_map( 'sanitize_user', explode( ',', $submission ) );
+ } elseif ( $pledge instanceof WP_Post ) {
+ // TODO the Contributor post type is being introduced in a separate PR. These details may change.
+
+ $contributor_posts = get_posts( array(
+ 'post_type' => '',
+ 'post_status' => array( 'pending', 'publish' ),
+ 'post_parent' => $pledge_id,
+ 'numberposts' => -1,
+ ) );
+
+ $contributors = wp_list_pluck( $contributor_posts, 'post_title' );
+ }
+
+ return $contributors;
+}
+
+/**
+ * Ensure each item in a list of usernames is valid and corresponds to a user.
+ *
+ * @param string $contributors A comma-separated list of username strings.
+ *
+ * @return array|WP_Error An array of sanitized wporg usernames on success. Otherwise WP_Error.
+ */
+function parse_contributors( $contributors ) {
+ $invalid_contributors = array();
+ $sanitized_contributors = array();
+
+ $contributors = explode( ',', $contributors );
+
+ foreach ( $contributors as $wporg_username ) {
+ $sanitized_username = sanitize_user( $wporg_username );
+ $user = get_user_by( 'login', $sanitized_username );
+
+ if ( $user instanceof WP_User ) {
+ $sanitized_contributors[] = $sanitized_username;
+ } else {
+ $invalid_contributors[] = $wporg_username;
+ }
+ }
+
+ if ( ! empty( $invalid_contributors ) ) {
+ /* translators: Used between sponsor names in a list, there is a space after the comma. */
+ $item_separator = _x( ', ', 'list item separator', 'wporg' );
+
+ return new WP_Error(
+ 'invalid_contributor',
+ sprintf(
+ /* translators: %s is a list of usernames. */
+ __( 'The following contributor usernames are not valid: %s', 'wporg' ),
+ implode( $item_separator, $invalid_contributors )
+ )
+ );
+ }
+
+ if ( empty( $sanitized_contributors ) ) {
+ return new WP_Error(
+ 'contributor_required',
+ __( 'The pledge must have at least one contributor username.', 'wporg' )
+ );
+ }
+
+ $sanitized_contributors = array_unique( $sanitized_contributors );
+
+ return $sanitized_contributors;
+}
+
/**
*
*
diff --git a/plugins/wporg-5ftf/includes/pledge-meta.php b/plugins/wporg-5ftf/includes/pledge-meta.php
index 1c29d22..a0c6194 100755
--- a/plugins/wporg-5ftf/includes/pledge-meta.php
+++ b/plugins/wporg-5ftf/includes/pledge-meta.php
@@ -7,17 +7,21 @@ namespace WordPressDotOrg\FiveForTheFuture\PledgeMeta;
use WordPressDotOrg\FiveForTheFuture;
use WordPressDotOrg\FiveForTheFuture\Pledge;
+use WordPressDotOrg\FiveForTheFuture\PledgeForm;
use WP_Post, WP_Error;
defined( 'WPINC' ) || die();
const META_PREFIX = FiveForTheFuture\PREFIX . '_';
-add_action( 'init', __NAMESPACE__ . '\register_pledge_meta' );
-add_action( 'admin_init', __NAMESPACE__ . '\add_meta_boxes' );
-add_action( 'save_post', __NAMESPACE__ . '\save_pledge', 10, 2 );
-add_action( 'updated_' . Pledge\CPT_ID . '_meta', __NAMESPACE__ . '\update_generated_meta', 10, 4 );
-add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_assets' );
+add_action( 'init', __NAMESPACE__ . '\register_pledge_meta' );
+add_action( 'admin_init', __NAMESPACE__ . '\add_meta_boxes' );
+add_action( 'save_post', __NAMESPACE__ . '\save_pledge', 10, 2 );
+add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_assets' );
+
+// Both hooks must be used because `updated` doesn't fire if the post meta didn't previously exist.
+add_action( 'updated_postmeta', __NAMESPACE__ . '\update_generated_meta', 10, 4 );
+add_action( 'added_post_meta', __NAMESPACE__ . '\update_generated_meta', 10, 4 );
/**
* Define pledge meta fields and their properties.
@@ -196,8 +200,7 @@ function save_pledge( $pledge_id, $pledge ) {
return;
}
- $definitions = wp_list_pluck( get_pledge_meta_config( 'user_input' ), 'php_filter' );
- $submitted_meta = filter_input_array( INPUT_POST, $definitions );
+ $submitted_meta = PledgeForm\get_form_submission();
if ( is_wp_error( has_required_pledge_meta( $submitted_meta ) ) ) {
return;
@@ -242,7 +245,21 @@ function save_pledge_meta( $pledge_id, $new_values ) {
* @return void
*/
function update_generated_meta( $meta_id, $object_id, $meta_key, $_meta_value ) {
+ $post_type = get_post_type( $object_id );
+
+ if ( Pledge\CPT_ID !== $post_type ) {
+ return;
+ }
+
switch ( $meta_key ) {
+ case META_PREFIX . 'org-name':
+ if ( 'updated_postmeta' === current_action() ) {
+ wp_update_post( array(
+ 'post_title' => $_meta_value,
+ ) );
+ }
+ break;
+
case META_PREFIX . 'org-url':
$domain = get_normalized_domain_from_url( $_meta_value );
update_post_meta( $object_id, META_PREFIX . 'org-domain', $domain );
@@ -291,26 +308,6 @@ function has_required_pledge_meta( array $submission ) {
return true;
}
-/**
- * Get the input filters for submitted content.
- *
- * @return array
- */
-function get_input_filters() {
- return array_merge(
- // Inputs that correspond to meta values.
- wp_list_pluck( get_pledge_meta_config( 'user_input' ), 'php_filter' ),
- // Inputs with no corresponding meta value.
- array(
- 'contributor-wporg-usernames' => [
- 'filter' => FILTER_SANITIZE_STRING,
- 'flags' => FILTER_REQUIRE_ARRAY,
- ],
- 'pledge-agreement' => FILTER_VALIDATE_BOOLEAN,
- )
- );
-}
-
/**
* Get the metadata for a given pledge, or a default set if no pledge is provided.
*
@@ -326,13 +323,13 @@ function get_pledge_meta( $pledge_id = 0, $context = '' ) {
$meta = array();
// Get POST'd submission, if it exists.
- $submission = filter_input_array( INPUT_POST, get_input_filters() );
+ $submission = PledgeForm\get_form_submission();
foreach ( $keys as $key => $config ) {
if ( isset( $submission[ $key ] ) ) {
$meta[ $key ] = $submission[ $key ];
- } else if ( $pledge instanceof WP_Post ) {
- $meta_key = META_PREFIX . $key;
+ } elseif ( $pledge instanceof WP_Post ) {
+ $meta_key = META_PREFIX . $key;
$meta[ $key ] = get_post_meta( $pledge->ID, $meta_key, true );
} else {
$meta[ $key ] = $config['default'] ?: '';
diff --git a/plugins/wporg-5ftf/views/inputs-pledge-contributors.php b/plugins/wporg-5ftf/views/inputs-pledge-contributors.php
index bc3fee3..96a13e2 100644
--- a/plugins/wporg-5ftf/views/inputs-pledge-contributors.php
+++ b/plugins/wporg-5ftf/views/inputs-pledge-contributors.php
@@ -16,7 +16,7 @@ namespace WordPressDotOrg\FiveForTheFuture\View;
type="text"
id="5ftf-pledge-contributors"
name="pledge-contributors"
- value=""
+ value=""
required
aria-describedby="5ftf-pledge-contributors-help"
/>
diff --git a/plugins/wporg-5ftf/views/inputs-pledge-new-misc.php b/plugins/wporg-5ftf/views/inputs-pledge-new-misc.php
index 2954f53..3b74b78 100644
--- a/plugins/wporg-5ftf/views/inputs-pledge-new-misc.php
+++ b/plugins/wporg-5ftf/views/inputs-pledge-new-misc.php
@@ -12,11 +12,12 @@ namespace WordPressDotOrg\FiveForTheFuture\View;
/>
-