Making WordPress.org


Ignore:
Timestamp:
11/03/2016 07:14:06 PM (8 years ago)
Author:
iandunn
Message:

CampTix Badge Generator: Add core functions for generating InDesign badges

See #262

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-badge-generator/includes/indesign-badges.php

    r3015 r4326  
    44use \CampTix\Badge_Generator;
    55use \CampTix\Badge_Generator\HTML;
     6use \WordCamp\Logger;
    67
    78defined( 'WPINC' ) or die();
     
    2122    require_once( dirname( __DIR__ ) . '/views/indesign-badges/page-indesign-badges.php' );
    2223}
     24
     25/**
     26 * Build the badge assets for InDesign
     27 */
     28function build_assets() {
     29    try {
     30        // Security: assets are intentionally saved to a folder outside the web root. See serve_zip_file() for details.
     31        $assets_folder    = sprintf( '%scamptix-badges-%d-%d', get_temp_dir(), get_current_blog_id(), time() );
     32        $gravatar_folder  = $assets_folder . '/gravatars';
     33        $csv_filename     = $assets_folder . '/attendees.csv';
     34        $zip_filename     = get_zip_filename( $assets_folder );
     35        $zip_local_folder = pathinfo( $zip_filename, PATHINFO_FILENAME );
     36        $attendees        = Badge_Generator\get_attendees();
     37
     38        wp_mkdir_p( $gravatar_folder );
     39        download_gravatars( $attendees, $gravatar_folder );
     40        generate_csv( $csv_filename, $zip_local_folder, $attendees, $gravatar_folder );
     41        create_zip_file( $zip_filename, $zip_local_folder, $csv_filename, $gravatar_folder );
     42    } finally {
     43        // todo Delete contents of $assets_folder, then rmdir( $assets_folder );
     44    }
     45}
     46
     47/**
     48 * Download each attendee's Gravatar
     49 *
     50 * @todo Remove set_time_limit() if end up running via a cron job
     51 *
     52 * @param array  $attendees
     53 * @param string $gravatar_folder
     54 *
     55 * @throws \Exception
     56 */
     57function download_gravatars( $attendees, $gravatar_folder ) {
     58    set_time_limit( 0 );
     59
     60    foreach ( $attendees as $attendee ) {
     61        if ( ! is_email( $attendee->tix_email ) ) {
     62            continue;
     63        }
     64
     65        $request_url = str_replace( '=blank', '=404', $attendee->avatar_url );
     66        $response    = wp_remote_get( $request_url );
     67        $image       = wp_remote_retrieve_body( $response );
     68        $status_code = wp_remote_retrieve_response_code( $response );
     69
     70        if ( 404 == $status_code ) {
     71            continue;
     72        }
     73
     74        if ( ! $image || 200 != $status_code ) {
     75            Logger\log( 'request_failed', compact( 'attendee', 'request_url', 'response' ) );
     76            throw new \Exception( __( "Couldn't download all Gravatars.", 'wordcamporg' ) );
     77        }
     78
     79        $filename      = get_gravatar_filename( $attendee );
     80        $gravatar_file = fopen( $gravatar_folder . '/' . $filename, 'w' );
     81
     82        fwrite( $gravatar_file, $image );
     83        fclose( $gravatar_file );
     84    }
     85}
     86
     87/**
     88 * Get the filename of the saved Gravatar for the given attendee
     89 *
     90 * @todo Returned value is false for input symbols like ♥, and maybe also for emoji
     91 *
     92 * @param \WP_Post $attendee
     93 *
     94 * @return string
     95 */
     96function get_gravatar_filename( $attendee ) {
     97    return sanitize_file_name( strtolower( sprintf(
     98        '%d-%s-%s.jpg',
     99        $attendee->ID,
     100        remove_accents( $attendee->tix_first_name ),
     101        remove_accents( $attendee->tix_last_name )
     102    ) ) );
     103}
     104
     105/**
     106 * Get the filename for the Zip file
     107 *
     108 * @param $assets_folder
     109 *
     110 * @return string
     111 */
     112function get_zip_filename( $assets_folder ) {
     113    return $zip_filename = sprintf(
     114        '%s/%s-badges.zip',
     115        $assets_folder,
     116        sanitize_file_name( sanitize_title( get_wordcamp_name() ) )
     117    );
     118}
     119
     120/**
     121 * Generate the CSV that InDesign will merge
     122 *
     123 * @todo Accept $destination_directory, $empty_twitter, and arbitrary tix_question fields from form input
     124 *
     125 * @todo Twitter username gets prefixed with ' by wcorg_esc_csv. Spreadsheet programs will ignore that, but
     126 * InDesign might not. If it doesn't, need to do something else to prevent the user having to manually remove
     127 * them.
     128 *
     129 * @param string $csv_filename
     130 * @param string $zip_local_folder
     131 * @param array  $attendees
     132 * @param string $gravatar_folder
     133 *
     134 * @throws \Exception
     135 */
     136function generate_csv( $csv_filename, $zip_local_folder, $attendees, $gravatar_folder ) {
     137    $csv_handle            = fopen( $csv_filename, 'w' );
     138    $destination_directory = "Macintosh HD:Users:your_username:Desktop:$zip_local_folder:gravatars:";
     139    $empty_twitter         = 'replace';
     140
     141    $header_row = array(
     142        'First Name', 'Last Name', 'Email Address', 'Ticket', 'Coupon', 'Twitter',
     143        '@Gravatar' // Prefixed with an @ to let InDesign know that it contains an image
     144    );
     145
     146    if ( ! $csv_handle ) {
     147        Logger\log( 'open_csv_failed' );
     148        throw new \Exception( __( "Couldn't open CSV file.", 'wordcamporg' ) );
     149    }
     150
     151    /*
     152     * Intentionally not escaping the header, because we need to preserve the `@` for InDesign. The values are all
     153     * hardcoded strings, so they're safe.
     154     */
     155    fputcsv( $csv_handle, $header_row );
     156
     157    foreach ( $attendees as $attendee ) {
     158        $row = get_attendee_csv_row( $attendee, $gravatar_folder, $destination_directory, $empty_twitter );
     159
     160        if ( empty( $row ) ) {
     161            continue;
     162        }
     163
     164        fputcsv( $csv_handle, wcorg_esc_csv( $row ) );
     165    }
     166
     167    fclose( $csv_handle );
     168}
     169
     170/**
     171 * Get the CSV row for the given attendee
     172 *
     173 * @param \WP_Post $attendee
     174 * @param string   $gravatar_folder
     175 * @param string   $destination_directory
     176 * @param string   $empty_twitter
     177 *
     178 * @return array
     179 */
     180function get_attendee_csv_row( $attendee, $gravatar_folder, $destination_directory, $empty_twitter ) {
     181    $row = array();
     182
     183    if ( 'unknown.attendee@example.org' == $attendee->tix_email ) {
     184        return $row;
     185    }
     186
     187    $gravatar_path     = '';
     188    $first_name        = ucwords( $attendee->tix_first_name );
     189    $gravatar_filename = get_gravatar_filename( $attendee );
     190
     191    if ( file_exists( $gravatar_folder .'/'. $gravatar_filename ) ) {
     192        $gravatar_path = $destination_directory . $gravatar_filename;
     193    }
     194
     195    $row = array(
     196        'first-name'       => $first_name,
     197        'last-name'        => ucwords( $attendee->tix_last_name ),
     198        'email-address'    => $attendee->tix_email,
     199        'ticket-name'      => $attendee->ticket,
     200        'coupon-name'      => $attendee->coupon,
     201        'twitter-username' => format_twitter_username( get_twitter_username( $attendee ), $first_name, $empty_twitter ),
     202        'gravatar-path'    => $gravatar_path,
     203    );
     204
     205    return $row;
     206}
     207
     208/**
     209 * Get an attendee's Twitter username
     210 *
     211 * @todo For DRY-ness, make this a public static method in CampTix_Addon_Twitter_Field and refactor
     212 * attendees_shortcode_item() to use it.
     213 *
     214 * @param \WP_Post $attendee
     215 *
     216 * @return string
     217 */
     218function get_twitter_username( $attendee ) {
     219    /** @global \CampTix_Plugin $camptix */
     220    global $camptix;
     221
     222    $username = '';
     223
     224    foreach ( $camptix->get_all_questions() as $question ) {
     225        if ( 'twitter' !== $question->tix_type ) {
     226            continue;
     227        }
     228
     229        if ( ! isset( $attendee->tix_questions[ $question->ID ] ) ) {
     230            continue;
     231        }
     232
     233        $username = trim( $attendee->tix_questions[ $question->ID ] );
     234        break;
     235    }
     236
     237    return $username;
     238}
     239
     240/**
     241 * Format a Twitter username
     242 *
     243 * @param string $username
     244 * @param string $first_name
     245 * @param string $empty_mode 'replace' to replace empty usernames with first names
     246 *
     247 * @return string
     248 */
     249function format_twitter_username( $username, $first_name, $empty_mode = 'replace' ) {
     250    if ( empty ( $username ) ) {
     251        if ( 'replace' === $empty_mode ) {
     252            $username = $first_name;
     253        }
     254    } else {
     255        // Strip out everything but the username, and prefix a @
     256        $username = '@' . preg_replace(
     257            '/
     258                (https?:\/\/)?
     259                (twitter\.com\/)?
     260                (@)?
     261            /ix',
     262            '',
     263            $username
     264        );
     265    }
     266
     267    return $username;
     268}
     269
     270/**
     271 * Create a Zip file with all of the assets
     272 *
     273 * @param string $zip_filename
     274 * @param string $zip_local_folder
     275 * @param string $csv_filename
     276 * @param string $gravatar_folder
     277 *
     278 * @throws \Exception
     279 */
     280function create_zip_file( $zip_filename, $zip_local_folder, $csv_filename, $gravatar_folder ) {
     281    if ( ! class_exists( 'ZipArchive') ) {
     282        Logger\log( 'zip_ext_not_installed' );
     283        throw new \Exception( __( 'The Zip extension for PHP is not installed.', 'wordcamporg' ) );
     284    }
     285
     286    $zip_file    = new \ZipArchive();
     287    $open_status = $zip_file->open( $zip_filename, \ZipArchive::OVERWRITE );
     288
     289    if ( true !== $open_status ) {
     290        Logger\log( 'zip_open_failed', compact( 'zip_filename', 'open_status' ) );
     291        throw new \Exception( __( 'The Zip file could not be created.', 'wordcamporg' ) );
     292    }
     293
     294    $zip_file->addFile(
     295        $csv_filename,
     296        trailingslashit( $zip_local_folder ) . basename( $csv_filename )
     297    );
     298
     299    $zip_file->addGlob(
     300        $gravatar_folder . '/*',
     301        0,
     302        array(
     303            'add_path'        => $zip_local_folder . '/gravatars/',
     304            'remove_all_path' => true
     305        )
     306    );
     307
     308    $zip_file->close();
     309}
     310
     311/**
     312 * Serve the Zip file for downloading
     313 *
     314 * Security: This is intentionally served through PHP instead of making it accessible directly through wp-content,
     315 * because the CSV file contains email addresses that we don't want to risk exposing to anyone scraping public
     316 * folders.
     317 *
     318 * @todo If run into problems, maybe look into disabling gzip and/or adding support for range requests.
     319 * See http://www.media-division.com/the-right-way-to-handle-file-downloads-in-php/
     320 *
     321 * @param string $filename
     322 *
     323 * @throws \Exception
     324 */
     325function serve_zip_file( $filename ) {
     326    if ( ! current_user_can( Badge_Generator\REQUIRED_CAPABILITY ) ) {
     327        Logger\log( 'access_denied' );
     328        throw new \Exception( __( "You don't have authorization to perform this action.", 'wordcamporg' ) );
     329    }
     330
     331    set_time_limit( 0 );
     332
     333    $headers = array(
     334        'Content-Type'        => 'application/octet-stream',
     335        'Content-Length'      => filesize( $filename ),
     336        'Content-Disposition' => sprintf( 'attachment; filename="%s"', basename( $filename ) ),
     337    );
     338
     339    foreach ( $headers as $header => $value ) {
     340        header( sprintf( '%s: %s', $header, $value ) );
     341    }
     342
     343    ob_clean();
     344    flush();
     345    readfile( $filename );
     346    die();
     347}
Note: See TracChangeset for help on using the changeset viewer.