<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[4326] sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-badge-generator/includes: CampTix Badge Generator: Add core functions for generating InDesign badges</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/4326">4326</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/4326","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>iandunn</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2016-11-03 19:14:06 +0000 (Thu, 03 Nov 2016)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>CampTix Badge Generator: Add core functions for generating InDesign badges

See <a href="http://meta.trac.wordpress.org/ticket/262">#262</a></pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixbadgegeneratorincludescommonphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-badge-generator/includes/common.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixbadgegeneratorincludesindesignbadgesphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-badge-generator/includes/indesign-badges.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixbadgegeneratorincludescommonphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-badge-generator/includes/common.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-badge-generator/includes/common.php       2016-11-03 10:10:35 UTC (rev 4325)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-badge-generator/includes/common.php 2016-11-03 19:14:06 UTC (rev 4326)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -104,7 +104,14 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        switch ( $meta_key ) {
</span><span class="cx" style="display: block; padding: 0 10px">                case 'avatar_url':
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $value = get_avatar_url( $attendee->tix_email, array( 'size' => 600 ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $value = get_avatar_url(
+                               $attendee->tix_email,
+                               array(
+                                       'size'    => 1024,
+                                       'default' => 'blank',
+                                       'rating'  => 'g'
+                               )
+                       );
</ins><span class="cx" style="display: block; padding: 0 10px">                         break;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                case 'coupon':
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginscamptixbadgegeneratorincludesindesignbadgesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-badge-generator/includes/indesign-badges.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-badge-generator/includes/indesign-badges.php      2016-11-03 10:10:35 UTC (rev 4325)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/camptix-badge-generator/includes/indesign-badges.php        2016-11-03 19:14:06 UTC (rev 4326)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3,6 +3,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> namespace CampTix\Badge_Generator\InDesign;
</span><span class="cx" style="display: block; padding: 0 10px"> use \CampTix\Badge_Generator;
</span><span class="cx" style="display: block; padding: 0 10px"> use \CampTix\Badge_Generator\HTML;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use \WordCamp\Logger;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> defined( 'WPINC' ) or die();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -20,3 +21,327 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        require_once( dirname( __DIR__ ) . '/views/indesign-badges/page-indesign-badges.php' );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Build the badge assets for InDesign
+ */
+function build_assets() {
+       try {
+               // Security: assets are intentionally saved to a folder outside the web root. See serve_zip_file() for details.
+               $assets_folder    = sprintf( '%scamptix-badges-%d-%d', get_temp_dir(), get_current_blog_id(), time() );
+               $gravatar_folder  = $assets_folder . '/gravatars';
+               $csv_filename     = $assets_folder . '/attendees.csv';
+               $zip_filename     = get_zip_filename( $assets_folder );
+               $zip_local_folder = pathinfo( $zip_filename, PATHINFO_FILENAME );
+               $attendees        = Badge_Generator\get_attendees();
+
+               wp_mkdir_p( $gravatar_folder );
+               download_gravatars( $attendees, $gravatar_folder );
+               generate_csv( $csv_filename, $zip_local_folder, $attendees, $gravatar_folder );
+               create_zip_file( $zip_filename, $zip_local_folder, $csv_filename, $gravatar_folder );
+       } finally {
+               // todo Delete contents of $assets_folder, then rmdir( $assets_folder );
+       }
+}
+
+/**
+ * Download each attendee's Gravatar
+ *
+ * @todo Remove set_time_limit() if end up running via a cron job
+ *
+ * @param array  $attendees
+ * @param string $gravatar_folder
+ *
+ * @throws \Exception
+ */
+function download_gravatars( $attendees, $gravatar_folder ) {
+       set_time_limit( 0 );
+
+       foreach ( $attendees as $attendee ) {
+               if ( ! is_email( $attendee->tix_email ) ) {
+                       continue;
+               }
+
+               $request_url = str_replace( '=blank', '=404', $attendee->avatar_url );
+               $response    = wp_remote_get( $request_url );
+               $image       = wp_remote_retrieve_body( $response );
+               $status_code = wp_remote_retrieve_response_code( $response );
+
+               if ( 404 == $status_code ) {
+                       continue;
+               }
+
+               if ( ! $image || 200 != $status_code ) {
+                       Logger\log( 'request_failed', compact( 'attendee', 'request_url', 'response' ) );
+                       throw new \Exception( __( "Couldn't download all Gravatars.", 'wordcamporg' ) );
+               }
+
+               $filename      = get_gravatar_filename( $attendee );
+               $gravatar_file = fopen( $gravatar_folder . '/' . $filename, 'w' );
+
+               fwrite( $gravatar_file, $image );
+               fclose( $gravatar_file );
+       }
+}
+
+/**
+ * Get the filename of the saved Gravatar for the given attendee
+ *
+ * @todo Returned value is false for input symbols like ♥, and maybe also for emoji
+ *
+ * @param \WP_Post $attendee
+ *
+ * @return string
+ */
+function get_gravatar_filename( $attendee ) {
+       return sanitize_file_name( strtolower( sprintf(
+               '%d-%s-%s.jpg',
+               $attendee->ID,
+               remove_accents( $attendee->tix_first_name ),
+               remove_accents( $attendee->tix_last_name )
+       ) ) );
+}
+
+/**
+ * Get the filename for the Zip file
+ *
+ * @param $assets_folder
+ *
+ * @return string
+ */
+function get_zip_filename( $assets_folder ) {
+       return $zip_filename = sprintf(
+               '%s/%s-badges.zip',
+               $assets_folder,
+               sanitize_file_name( sanitize_title( get_wordcamp_name() ) )
+       );
+}
+
+/**
+ * Generate the CSV that InDesign will merge
+ *
+ * @todo Accept $destination_directory, $empty_twitter, and arbitrary tix_question fields from form input
+ *
+ * @todo Twitter username gets prefixed with ' by wcorg_esc_csv. Spreadsheet programs will ignore that, but
+ * InDesign might not. If it doesn't, need to do something else to prevent the user having to manually remove
+ * them.
+ *
+ * @param string $csv_filename
+ * @param string $zip_local_folder
+ * @param array  $attendees
+ * @param string $gravatar_folder
+ *
+ * @throws \Exception
+ */
+function generate_csv( $csv_filename, $zip_local_folder, $attendees, $gravatar_folder ) {
+       $csv_handle            = fopen( $csv_filename, 'w' );
+       $destination_directory = "Macintosh HD:Users:your_username:Desktop:$zip_local_folder:gravatars:";
+       $empty_twitter         = 'replace';
+
+       $header_row = array(
+               'First Name', 'Last Name', 'Email Address', 'Ticket', 'Coupon', 'Twitter',
+               '@Gravatar' // Prefixed with an @ to let InDesign know that it contains an image
+       );
+
+       if ( ! $csv_handle ) {
+               Logger\log( 'open_csv_failed' );
+               throw new \Exception( __( "Couldn't open CSV file.", 'wordcamporg' ) );
+       }
+
+       /*
+        * Intentionally not escaping the header, because we need to preserve the `@` for InDesign. The values are all
+        * hardcoded strings, so they're safe.
+        */
+       fputcsv( $csv_handle, $header_row );
+
+       foreach ( $attendees as $attendee ) {
+               $row = get_attendee_csv_row( $attendee, $gravatar_folder, $destination_directory, $empty_twitter );
+
+               if ( empty( $row ) ) {
+                       continue;
+               }
+
+               fputcsv( $csv_handle, wcorg_esc_csv( $row ) );
+       }
+
+       fclose( $csv_handle );
+}
+
+/**
+ * Get the CSV row for the given attendee
+ *
+ * @param \WP_Post $attendee
+ * @param string   $gravatar_folder
+ * @param string   $destination_directory
+ * @param string   $empty_twitter
+ *
+ * @return array
+ */
+function get_attendee_csv_row( $attendee, $gravatar_folder, $destination_directory, $empty_twitter ) {
+       $row = array();
+
+       if ( 'unknown.attendee@example.org' == $attendee->tix_email ) {
+               return $row;
+       }
+
+       $gravatar_path     = '';
+       $first_name        = ucwords( $attendee->tix_first_name );
+       $gravatar_filename = get_gravatar_filename( $attendee );
+
+       if ( file_exists( $gravatar_folder .'/'. $gravatar_filename ) ) {
+               $gravatar_path = $destination_directory . $gravatar_filename;
+       }
+
+       $row = array(
+               'first-name'       => $first_name,
+               'last-name'        => ucwords( $attendee->tix_last_name ),
+               'email-address'    => $attendee->tix_email,
+               'ticket-name'      => $attendee->ticket,
+               'coupon-name'      => $attendee->coupon,
+               'twitter-username' => format_twitter_username( get_twitter_username( $attendee ), $first_name, $empty_twitter ),
+               'gravatar-path'    => $gravatar_path,
+       );
+
+       return $row;
+}
+
+/**
+ * Get an attendee's Twitter username
+ *
+ * @todo For DRY-ness, make this a public static method in CampTix_Addon_Twitter_Field and refactor
+ * attendees_shortcode_item() to use it.
+ *
+ * @param \WP_Post $attendee
+ *
+ * @return string
+ */
+function get_twitter_username( $attendee ) {
+       /** @global \CampTix_Plugin $camptix */
+       global $camptix;
+
+       $username = '';
+
+       foreach ( $camptix->get_all_questions() as $question ) {
+               if ( 'twitter' !== $question->tix_type ) {
+                       continue;
+               }
+
+               if ( ! isset( $attendee->tix_questions[ $question->ID ] ) ) {
+                       continue;
+               }
+
+               $username = trim( $attendee->tix_questions[ $question->ID ] );
+               break;
+       }
+
+       return $username;
+}
+
+/**
+ * Format a Twitter username
+ *
+ * @param string $username
+ * @param string $first_name
+ * @param string $empty_mode 'replace' to replace empty usernames with first names
+ *
+ * @return string
+ */
+function format_twitter_username( $username, $first_name, $empty_mode = 'replace' ) {
+       if ( empty ( $username ) ) {
+               if ( 'replace' === $empty_mode ) {
+                       $username = $first_name;
+               }
+       } else {
+               // Strip out everything but the username, and prefix a @
+               $username = '@' . preg_replace(
+                       '/
+                               (https?:\/\/)?
+                               (twitter\.com\/)?
+                               (@)?
+                       /ix',
+                       '',
+                       $username
+               );
+       }
+
+       return $username;
+}
+
+/**
+ * Create a Zip file with all of the assets
+ *
+ * @param string $zip_filename
+ * @param string $zip_local_folder
+ * @param string $csv_filename
+ * @param string $gravatar_folder
+ *
+ * @throws \Exception
+ */
+function create_zip_file( $zip_filename, $zip_local_folder, $csv_filename, $gravatar_folder ) {
+       if ( ! class_exists( 'ZipArchive') ) {
+               Logger\log( 'zip_ext_not_installed' );
+               throw new \Exception( __( 'The Zip extension for PHP is not installed.', 'wordcamporg' ) );
+       }
+
+       $zip_file    = new \ZipArchive();
+       $open_status = $zip_file->open( $zip_filename, \ZipArchive::OVERWRITE );
+
+       if ( true !== $open_status ) {
+               Logger\log( 'zip_open_failed', compact( 'zip_filename', 'open_status' ) );
+               throw new \Exception( __( 'The Zip file could not be created.', 'wordcamporg' ) );
+       }
+
+       $zip_file->addFile(
+               $csv_filename,
+               trailingslashit( $zip_local_folder ) . basename( $csv_filename )
+       );
+
+       $zip_file->addGlob(
+               $gravatar_folder . '/*',
+               0,
+               array(
+                       'add_path'        => $zip_local_folder . '/gravatars/',
+                       'remove_all_path' => true
+               )
+       );
+
+       $zip_file->close();
+}
+
+/**
+ * Serve the Zip file for downloading
+ *
+ * Security: This is intentionally served through PHP instead of making it accessible directly through wp-content,
+ * because the CSV file contains email addresses that we don't want to risk exposing to anyone scraping public
+ * folders.
+ *
+ * @todo If run into problems, maybe look into disabling gzip and/or adding support for range requests.
+ * See http://www.media-division.com/the-right-way-to-handle-file-downloads-in-php/
+ *
+ * @param string $filename
+ *
+ * @throws \Exception
+ */
+function serve_zip_file( $filename ) {
+       if ( ! current_user_can( Badge_Generator\REQUIRED_CAPABILITY ) ) {
+               Logger\log( 'access_denied' );
+               throw new \Exception( __( "You don't have authorization to perform this action.", 'wordcamporg' ) );
+       }
+
+       set_time_limit( 0 );
+
+       $headers = array(
+               'Content-Type'        => 'application/octet-stream',
+               'Content-Length'      => filesize( $filename ),
+               'Content-Disposition' => sprintf( 'attachment; filename="%s"', basename( $filename ) ),
+       );
+
+       foreach ( $headers as $header => $value ) {
+               header( sprintf( '%s: %s', $header, $value ) );
+       }
+
+       ob_clean();
+       flush();
+       readfile( $filename );
+       die();
+}
</ins></span></pre>
</div>
</div>

</body>
</html>