Making WordPress.org


Ignore:
Timestamp:
11/03/2016 07:14:06 PM (10 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.