From 0f1c5feeabbe1a7889c0f2426f09fb0f10b0e4bc Mon Sep 17 00:00:00 2001 From: Ren <18050944+renintw@users.noreply.github.com> Date: Tue, 26 Sep 2023 21:18:59 +0800 Subject: [PATCH] Add organizer debrief survey (#1038) * Add plugin: WordCamp Organizer Survey. * Add cron job. * Add camptix email. * Make plugin unable to be network deactivated. * Embed a token in URL for verifying organizer. * Fill in data that we already know. * Fix tix_email stats counting error. --- .docker/wp-config.php | 17 +- .../wcorg-network-plugin-control.php | 1 + .../wp-content/plugins/camptix/camptix.php | 3 + .../includes/debrief-survey/cron.php | 184 ++++++++++++++++++ .../includes/debrief-survey/email.php | 151 ++++++++++++++ .../wordcamp-organizer-survey.php | 178 +++++++++++++++++ 6 files changed, 526 insertions(+), 8 deletions(-) create mode 100644 public_html/wp-content/plugins/wordcamp-organizer-survey/includes/debrief-survey/cron.php create mode 100644 public_html/wp-content/plugins/wordcamp-organizer-survey/includes/debrief-survey/email.php create mode 100644 public_html/wp-content/plugins/wordcamp-organizer-survey/wordcamp-organizer-survey.php diff --git a/.docker/wp-config.php b/.docker/wp-config.php index 289d8d061..928334333 100644 --- a/.docker/wp-config.php +++ b/.docker/wp-config.php @@ -89,14 +89,15 @@ * environments, because generating the keys locally is safer than using the API (and exposing the keys to * your OS/browser if you copy/paste, etc). */ -define( 'AUTH_KEY', 'put your unique phrase here' ); -define( 'SECURE_AUTH_KEY', 'put your unique phrase here' ); -define( 'LOGGED_IN_KEY', 'put your unique phrase here' ); -define( 'NONCE_KEY', 'put your unique phrase here' ); -define( 'AUTH_SALT', 'put your unique phrase here' ); -define( 'SECURE_AUTH_SALT', 'put your unique phrase here' ); -define( 'LOGGED_IN_SALT', 'put your unique phrase here' ); -define( 'NONCE_SALT', 'put your unique phrase here' ); +define( 'AUTH_KEY', 'put your unique phrase here' ); +define( 'SECURE_AUTH_KEY', 'put your unique phrase here' ); +define( 'LOGGED_IN_KEY', 'put your unique phrase here' ); +define( 'NONCE_KEY', 'put your unique phrase here' ); +define( 'AUTH_SALT', 'put your unique phrase here' ); +define( 'SECURE_AUTH_SALT', 'put your unique phrase here' ); +define( 'LOGGED_IN_SALT', 'put your unique phrase here' ); +define( 'NONCE_SALT', 'put your unique phrase here' ); +define( 'ORGANIZER_SURVEY_ACCESS_TOKEN_KEY', 'put your unique phrase here' ); /* diff --git a/public_html/wp-content/mu-plugins/wcorg-network-plugin-control.php b/public_html/wp-content/mu-plugins/wcorg-network-plugin-control.php index 1e8d0003b..24e6e1173 100644 --- a/public_html/wp-content/mu-plugins/wcorg-network-plugin-control.php +++ b/public_html/wp-content/mu-plugins/wcorg-network-plugin-control.php @@ -94,6 +94,7 @@ function _get_network_plugin_state_list( $state ) { } elseif ( EVENTS_NETWORK_ID === $network_id ) { $network_plugin_state['deactivated'][] = 'tagregator/bootstrap.php'; $network_plugin_state['activated'][] = 'camptix-attendee-survey/camptix-attendee-survey.php'; + $network_plugin_state['activated'][] = 'wordcamp-organizer-survey/wordcamp-organizer-survey.php'; } return $network_plugin_state[ $state ]; diff --git a/public_html/wp-content/plugins/camptix/camptix.php b/public_html/wp-content/plugins/camptix/camptix.php index 847b994fc..8285d8d15 100644 --- a/public_html/wp-content/plugins/camptix/camptix.php +++ b/public_html/wp-content/plugins/camptix/camptix.php @@ -823,6 +823,9 @@ function manage_columns_email_action( $column, $post_id ) { break; case 'tix_total': $recipients_backup = get_post_meta( $post_id, 'tix_email_recipients_backup', true ); + if( empty( $recipients_backup ) ) { + $recipients_backup = []; + } echo count( $recipients_backup ); break; } diff --git a/public_html/wp-content/plugins/wordcamp-organizer-survey/includes/debrief-survey/cron.php b/public_html/wp-content/plugins/wordcamp-organizer-survey/includes/debrief-survey/cron.php new file mode 100644 index 000000000..49e95db9f --- /dev/null +++ b/public_html/wp-content/plugins/wordcamp-organizer-survey/includes/debrief-survey/cron.php @@ -0,0 +1,184 @@ +log( $message, $post_id, $data ); +} + +/** + * Get Wordcamp attendee ID. + * + * @return int + */ +function get_wordcamp_attendees_id() { + return get_option( TEMP_ATTENDEE_ID ); +} + +/** + * Add cron jobs to the schedule. + */ +function schedule_jobs() { + if ( ! wp_next_scheduled( 'wc_organizer_debrief_survey_email' ) ) { + wp_schedule_event( time(), 'daily', 'wc_organizer_debrief_survey_email' ); + } +} + +/** + * Add temporary attendee since email can only be sent to items in attendee tracker. + */ +function add_temp_attendee() { + if ( ! get_wordcamp_attendees_id() ) { + $attendees_id = wp_insert_post( array( + 'post_title' => 'Organizer debrief survey', + 'post_name' => 'organizer-debrief-survey', + 'post_type' => 'tix_attendee', + 'post_status' => 'publish', + ) ); + + if ( $attendees_id ) { + update_post_meta( $attendees_id, 'tix_email', get_lead_organizer_email() ); + update_post_meta( $attendees_id, 'tix_receipt_email', get_lead_organizer_email() ); + update_post_meta( $attendees_id, 'tix_first_name', get_lead_organizer_full_name() ); + + update_option( TEMP_ATTENDEE_ID, $attendees_id ); + log( 'Successfully added attendee:', get_email_id(), $attendees_id ); + } else { + log( 'Failed to add attendee:', get_email_id(), $attendees_id ); + } + } +} + +/** + * Associates attendee to emails. + */ +function associate_attendee_to_email( $email_id ) { + $recipient_id = get_wordcamp_attendees_id(); + + if ( empty( $recipient_id ) ) { + log( 'No valid recipients', $email_id, null ); + return; + } + + // Associate attendee to tix_email as a recipient. + $result = add_post_meta( $email_id, 'tix_email_recipient_id', $recipient_id ); + + if ( ! $result ) { + log( 'Failed to add recipients:', $email_id, $recipient_id ); + } else { + update_post_meta( $email_id, 'tix_email_recipients_backup', (array) $recipient_id ); + log( 'Successfully added recipients:', $email_id, $recipient_id ); + } +} + +/** + * Returns true if an emailed will be sent or is queued. + * + * @return bool + */ +function is_email_already_sent_or_queued( $email_id ) { + $email = get_post( $email_id ); + return 'publish' === $email->post_status || 'pending' === $email->post_status; +} + +/** + * Returns true if it is time to send the email. + * + * @return bool + */ +function is_time_to_send_email( $email_id ) { + $blog_id = get_current_blog_id(); + $wordcamp_post = get_wordcamp_post( $blog_id ); + + if ( ! $wordcamp_post ) { + log( 'Couldn\'t retrieve wordcamp for blog id:', $email_id, $blog_id ); + return false; + } + + $end_date = $wordcamp_post->meta['End Date (YYYY-mm-dd)'][0]; + + if ( empty( $end_date ) ) { + // Some sites that are only 1-day events will only have the start date. + $end_date = $wordcamp_post->meta['Start Date (YYYY-mm-dd)'][0]; + } + + $date = new \DateTime("@$end_date "); + $current_date = new \DateTime(); + $interval = $current_date->diff($date); + $days_difference = $interval->days; + + return DAYS_AFTER_TO_SEND === $days_difference; +} + +/** + * Delete the temporary attendee. + */ +function delete_temp_attendee() { + $attendee_id = get_wordcamp_attendees_id(); + wp_delete_post( $attendee_id, true ); + delete_option( TEMP_ATTENDEE_ID ); +} + +/** + * Associates recipients to email and changes its status to be picked up + * by the camptix email cron job `tix_scheduled_every_ten_minutes`. + */ +function queue_organizer_survey() { + $email_id = get_email_id(); + + if ( empty( $email_id ) ) { + return; + } + + // check to make sure we didn't already send an email for this wordcamp. + if ( is_email_already_sent_or_queued( $email_id ) ) { + return; + } + + if ( ! is_time_to_send_email( $email_id ) ) { + return; + } + + add_temp_attendee(); + associate_attendee_to_email( $email_id ); + + $email_status = queue_survey_email( $email_id ); + + if ( is_wp_error( $email_status ) ) { + log( 'Failed updating email status', $email_id, $email_status ); + } else { + log( 'Email status change to `pending`.', $email_id ); + } + + // Remove the cron job that queues everything. + wp_clear_scheduled_hook( 'wc_organizer_debrief_survey_email' ); +} diff --git a/public_html/wp-content/plugins/wordcamp-organizer-survey/includes/debrief-survey/email.php b/public_html/wp-content/plugins/wordcamp-organizer-survey/includes/debrief-survey/email.php new file mode 100644 index 000000000..5dc34cf4c --- /dev/null +++ b/public_html/wp-content/plugins/wordcamp-organizer-survey/includes/debrief-survey/email.php @@ -0,0 +1,151 @@ +meta; + return $meta['Email Address'][0]; +} + +/** + * Return lead organizer full name. + * + * @return string + */ +function get_lead_organizer_full_name() { + $meta = get_wordcamp_post()->meta; + return $meta['Organizer Name'][0]; +} + +/** + * Return WordCamp location. + * + * @return string + */ +function get_wordcamp_location() { + $meta = get_wordcamp_post()->meta; + return $meta['Location'][0]; +} + + +/** + * Return a token to put into the survey URL. + * + * @return string + */ +function get_verification_token() { + $wordcamp_id = get_current_blog_id(); + return hash_hmac( 'sha256', $wordcamp_id, ORGANIZER_SURVEY_ACCESS_TOKEN_KEY ); +} + +/** + * Returns content for the reminder email. + * + * @return string + */ +function get_email_content() { + $wordcamp_name = get_wordcamp_name(); + $tld = get_top_level_domain(); + $survey_page_url = "https://central.wordcamp.$tld/organizer-survey-event-debrief/?t=" . get_verification_token() + . '&wid=' . get_current_blog_id(); + + $email = "Howdy [email],\r\n\r\n"; + $email .= sprintf( "Congratulations on completing %s! We hope you had a great time, and that you'll soon get some well-deserved rest\r\n\r\n", esc_html( $wordcamp_name ) ); + + $email .= "We'd love to hear how you feel the event went. What were your proudest moments and your greatest disappointments?\r\n"; + $email .= sprintf( "We've created an Organizer Survey, so we can get all the details of how things went with your event. Please fill it out this form within 10 days: %s\r\n\r\n", esc_url( $survey_page_url ) ); + + $email .= "Event Budget\r\n"; + $email .= 'Please update your working budget on your event dashboard. If you ran your money outside of WordPress Community Support, PBC, please also balance your budget spreadsheet and prepare your Transparency Report.'; + $email .= "If there are any issues, please reach out to us at support@wordcamp.org to schedule a budget close-out meeting. Please complete these steps within the next two weeks.\r\n\r\n"; + + $email .= "Event Recording (if any)\r\n"; + $email .= sprintf( "If you haven't yet done so, please review the submission guidelines before beginning to edit your videos (or before your videographers starts editing): %s\r\n\r\n", esc_url( 'http://blog.wordpress.tv/submission-guidelines/' ) ); + $email .= sprintf( "To submit your video for publication to WordPress.tv, just upload them at this page: %s\r\n\r\n", esc_url( 'http://wordpress.tv/submit-video/' ) ); + $email .= 'Our intrepid team of video moderators will review the videos and schedule them for publication. '; + $email .= sprintf( "If your content is in a language other than English, please see if you can recruit someone from your community to join the WordPress TV moderators' team and review your videos: %s\r\n\r\n", esc_url( 'http://wordpress.tv/apply-to-be-a-wordpress-tv-moderator/' ) ); + + $email .= "Event Recap\r\n"; + $email .= "Finally, if you've published a recap on your site, please let us know, so we can reblog it on the WordCamp Central blog.\r\n\r\n"; + + $email .= sprintf( "Thanks again for all you've done to grow the WordPress community in %s!\r\n\r\n", get_wordcamp_location() ); + $email .= "Best wishes,\r\n"; + $email .= 'Your friendly WordCamp Central crew'; + + return $email; +} + +/** + * Adds email to camptix email queue. + */ +function add_email() { + $email_id = get_option( EMAIL_KEY_ID ); + if ( $email_id || ! get_wordcamp_post() ) { + return; + } + + $email_id = wp_insert_post( + array( + 'post_title' => __( 'Organizer Survey (event debrief)', 'wordcamporg' ), + 'post_content' => get_email_content(), + 'post_status' => 'draft', + 'post_type' => 'tix_email', + ) + ); + + if ( $email_id > 0 ) { + update_option( EMAIL_KEY_ID, $email_id ); + } +} + +/** + * Turns on the email by changing its status to 'pending'. + * + * The Camptix email queue will send the email. + * + * @return int|WP_Error + */ +function queue_survey_email( $email_id ) { + return wp_update_post( array( + 'ID' => $email_id, + 'post_status' => 'pending', + ) ); +} + +/** + * Delete the email and associated meta data. + */ +function delete_email() { + $email_id = get_email_id(); + // Clean up any associated attendees to the email. + delete_post_meta( $email_id, 'tix_email_recipient_id' ); + delete_post_meta( $email_id, 'tix_email_recipients_backup' ); + wp_delete_post( $email_id, true ); + delete_option( EMAIL_KEY_ID ); +} diff --git a/public_html/wp-content/plugins/wordcamp-organizer-survey/wordcamp-organizer-survey.php b/public_html/wp-content/plugins/wordcamp-organizer-survey/wordcamp-organizer-survey.php new file mode 100644 index 000000000..d78a18a06 --- /dev/null +++ b/public_html/wp-content/plugins/wordcamp-organizer-survey/wordcamp-organizer-survey.php @@ -0,0 +1,178 @@ +get_col( + $wpdb->prepare(" + SELECT b.blog_id + FROM $wpdb->blogs AS b + LEFT OUTER JOIN $wpdb->blogmeta AS m + ON b.blog_id = m.blog_id AND m.meta_value = %s + ", + get_feature_id() + ) + ); + + return array_map( 'absint', $blog_ids ); +} + +/** + * Shortcut to the includes directory. + * + * @return string + */ +function get_includes_path() { + return plugin_dir_path( __FILE__ ) . 'includes/'; +} + +/** + * Validate token on debrief survey page to check if it's an organizer visiting. + */ +function validate_token_on_debrief_survey() { + global $wordcamp_post_data; + + if ( is_page( 'organizer-survey-event-debrief' ) ) { + $token = $_GET['t'] ?? ''; + $wordcamp_id = $_GET['wid'] ?? ''; + + $expected_token = hash_hmac( 'sha256', $wordcamp_id, ORGANIZER_SURVEY_ACCESS_TOKEN_KEY ); + + // Check if the request is a form submission. If not, then validate the token. + if ( 'POST' !== $_SERVER['REQUEST_METHOD'] && ! hash_equals( $expected_token, $token ) ) { + wp_die('Invalid access token.'); + } else { + $wordcamp_post_data = get_wordcamp_post( $wordcamp_id ); + } + } +} + +/** + * Modifies the Jetpack contact form content. + * + * @param string $content The original content of the post or page. + * @return string Modified content with the updated Jetpack contact form. + */ +function modify_jetpack_contact_form( $content ) { + global $wordcamp_post_data; + + if ( is_page( 'organizer-survey-event-debrief' ) ) { + $dom = new \DOMDocument(); + $dom->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + + $labels = $dom->getElementsByTagName('label'); + foreach ( $labels as $label ) { + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + if ( 'What event did you organize?' === $label->nodeValue ) { + $input_id = $label->getAttribute('for'); + $event_name_field = $dom->getElementById($input_id); + $event_name_field->setAttribute('value', $wordcamp_post_data->post_title); + } + } + $content = $dom->saveHTML(); + } + return $content; +}