<!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>[6268] sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types: WordCamp Post Types: Add ability for attendees to "favorite" sessions.</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/6268">6268</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/6268","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>2017-12-13 22:42:47 +0000 (Wed, 13 Dec 2017)</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'>WordCamp Post Types: Add ability for attendees to "favorite" sessions.
This contains the code for emailing the saved sessions to yourself, but the UI for that is temporarily disabled pending a discussion with the Systems team about potential abuse mitigations.
See <a href="http://meta.trac.wordpress.org/ticket/2733">#2733</a>
Props egmanekki</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswcposttypescssshortcodescss">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/css/shortcodes.css</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswcposttypesincrestapiphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/inc/rest-api.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswcposttypeswcposttypesphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/wc-post-types.php</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswcposttypesincfavoritescheduleshortcodephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/inc/favorite-schedule-shortcode.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswcposttypesjsfavouritesessionsjs">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/js/favourite-sessions.js</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswcposttypescssshortcodescss"></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/wc-post-types/css/shortcodes.css</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/wc-post-types/css/shortcodes.css 2017-12-13 21:58:47 UTC (rev 6267)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/css/shortcodes.css 2017-12-13 22:42:47 UTC (rev 6268)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,6 +1,179 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /*
</span><span class="cx" style="display: block; padding: 0 10px"> * [schedule]
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ .wcb-favourite-session {
+ background: #e0f8ff;
+}
+
+.wcpt-schedule td {
+ vertical-align: top;
+}
+
+.wcpt-schedule div.wcb-session-favourite-icon {
+ float: right;
+ width: 35px;
+ text-align: center;
+}
+
+.wcpt-schedule .dashicons {
+ position: relative;
+ box-sizing: content-box;
+ width: 25px;
+ height: 25px;
+ overflow: hidden;
+ white-space: nowrap;
+ font-size: 16px;
+ line-height: 1;
+ cursor: pointer;
+}
+
+.wcpt-schedule .dashicons:before {
+ margin-right: 0px;
+}
+
+.wcpt-schedule .dashicons:after {
+ display: block;
+ font-size: 9px;
+ color: #999;
+ text-align: right;
+}
+
+
+div.wcb-session-favourite-icon a.fav-session-button {
+ color: #e8e8e8;
+ text-decoration: none;
+}
+
+
+div.wcb-session-favourite-icon a.fav-session-button:hover,
+#content a.fav-session-button:hover {
+ color: #fff689;
+ text-decoration: none;
+}
+
+
+td.wcb-favourite-session a.fav-session-button {
+ color: #fff689;
+}
+
+.fav-session-email-wait-spinner {
+ display: none;
+ border: 2px solid #f3f3f3;
+ border-radius: 50%;
+ border-top: 2px solid #777;
+ width: 16px;
+ height: 16px;
+ margin: 10px auto;
+ -webkit-animation: spin 2s linear infinite;
+ animation: spin 2s linear infinite;
+}
+
+@-webkit-keyframes spin {
+ 0% { -webkit-transform: rotate( 0deg ); }
+ 100% { -webkit-transform: rotate( 360deg ); }
+}
+
+@keyframes spin {
+ 0% { transform: rotate( 0deg ); }
+ 100% { transform: rotate( 360deg ); }
+}
+
+
+/*
+ * CSS slide for email form for favourite sessions
+ */
+.fav-session-email-form-hide {
+ overflow: hidden;
+ max-height: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ -moz-transition-duration: 0.5s;
+ -webkit-transition-duration: 0.5s;
+ -o-transition-duration: 0.5s;
+ transition-duration: 0.5s;
+ -moz-transition-timing-function: cubic-bezier( 0, 1, 0.5, 1 );
+ -webkit-transition-timing-function: cubic-bezier( 0, 1, 0.5, 1 );
+ -o-transition-timing-function: cubic-bezier( 0, 1, 0.5, 1 );
+ transition-timing-function: cubic-bezier( 0, 1, 0.5, 1 );
+}
+
+.fav-session-email-form-show {
+ -moz-transition-duration: 0.5s;
+ -webkit-transition-duration: 0.5s;
+ -o-transition-duration: 0.5s;
+ transition-duration: 0.5s;
+ -moz-transition-timing-function: ease-in;
+ -webkit-transition-timing-function: ease-in;
+ -o-transition-timing-function: ease-in;
+ transition-timing-function: ease-in;
+ max-height: 1000px;
+ overflow: hidden;
+}
+
+.show-email-form {
+ display: none;
+ position: fixed;
+ bottom: 20px;
+ right: 100px;
+ padding: 5px 8px 1px 7px;
+ border: 1px solid #a1a1a1;
+ background: #dddddd;
+ border-radius: 6px;
+ color: #a1a1a1;
+ z-index: 9999;
+}
+
+.email-form {
+ position: fixed;
+ bottom: 50px;
+ right: 100px;
+ width: 200px;
+ background: #dcdcdc;
+ font-size: 12px;
+ z-index: 9999;
+ border-radius: 6px;
+}
+
+#fav-session-email-form {
+ margin: 10px;
+}
+
+#fav-sessions-email-address {
+ width: 100%;
+ margin-bottom: 5px;
+ padding: 1px 5px;
+}
+
+.fav-session-email-result {
+ display: none;
+ margin: 10px;
+}
+
+.fav-sessions-form {
+ display: none;
+}
+
+.show-email-form,
+.entry-content a.show-email-form {
+ color: #a1a1a1;
+ text-decoration: none;
+}
+
+.show-email-form:hover,
+.entry-content a.show-email-form:hover,
+#content a.show-email-form:hover {
+ color: #555;
+ text-decoration: none;
+}
+
+.show-email-form .dashicons-star-filled {
+ font-size: 14px;
+ padding-top: 2px;
+ width: 14px;
+}
+
</ins><span class="cx" style="display: block; padding: 0 10px"> @media screen and ( max-width: 700px ) {
</span><span class="cx" style="display: block; padding: 0 10px"> .wcpt-schedule {
</span><span class="cx" style="display: block; padding: 0 10px"> border: none;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -72,6 +245,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> span.wcpt-session-speakers a {
</span><span class="cx" style="display: block; padding: 0 10px"> color: #21759b;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ .show-email-form,
+ .email-form {
+ right: 20px;
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /*
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswcposttypesincfavoritescheduleshortcodephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/inc/favorite-schedule-shortcode.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/wc-post-types/inc/favorite-schedule-shortcode.php (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/inc/favorite-schedule-shortcode.php 2017-12-13 22:42:47 UTC (rev 6268)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,432 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * [schedule] shortcode building blocks and favourite session picker support.
+ */
+
+defined( 'WPINC' ) || die();
+
+/**
+ * Return an associative array of term_id -> term object mapping for all selected tracks.
+ *
+ * In case of 'all' is used as a value for $selected_tracks, information for all available tracks
+ * gets returned.
+ *
+ * @param string $selected_tracks Comma-separated list of tracks to display or 'all'.
+ *
+ * @return array Associative array of terms with term_id as the key.
+ */
+function get_schedule_tracks( $selected_tracks ) {
+ $tracks = array();
+ if ( 'all' === $selected_tracks ) {
+ // Include all tracks.
+ $tracks = get_terms( 'wcb_track' );
+ } else {
+ // Loop through given tracks and look for terms.
+ $terms = array_map( 'trim', explode( ',', $selected_tracks ) );
+
+ foreach ( $terms as $term_slug ) {
+ $term = get_term_by( 'slug', $term_slug, 'wcb_track' );
+ if ( $term ) {
+ $tracks[ $term->term_id ] = $term;
+ }
+ }
+ }
+
+ return $tracks;
+}
+
+/**
+ * Return a time-sorted associative array mapping timestamp -> track_id -> session id.
+ *
+ * @param string $schedule_date Date for which the sessions should be retrieved.
+ * @param bool $tracks_explicitly_specified True if tracks were explicitly specified in the shortcode,
+ * false otherwise.
+ * @param array $tracks Array of terms for tracks from get_schedule_tracks().
+ *
+ * @return array Associative array of session ids by time and track.
+ */
+function get_schedule_sessions( $schedule_date, $tracks_explicitly_specified, $tracks ) {
+ $query_args = array(
+ 'post_type' => 'wcb_session',
+ 'posts_per_page' => - 1,
+ 'meta_query' => array(
+ 'relation' => 'AND',
+ array(
+ 'key' => '_wcpt_session_time',
+ 'compare' => 'EXISTS',
+ ),
+ ),
+ );
+
+ if ( $schedule_date && strtotime( $schedule_date ) ) {
+ $query_args['meta_query'][] = array(
+ 'key' => '_wcpt_session_time',
+ 'value' => array(
+ strtotime( $schedule_date ),
+ strtotime( $schedule_date . ' +1 day' ),
+ ),
+ 'compare' => 'BETWEEN',
+ 'type' => 'NUMERIC',
+ );
+ }
+
+ if ( $tracks_explicitly_specified ) {
+ // If tracks were provided, restrict the lookup in WP_Query.
+ if ( ! empty( $tracks ) ) {
+ $query_args['tax_query'][] = array(
+ 'taxonomy' => 'wcb_track',
+ 'field' => 'id',
+ 'terms' => array_values( wp_list_pluck( $tracks, 'term_id' ) ),
+ );
+ }
+ }
+
+ // Loop through all sessions and assign them into the formatted
+ // $sessions array: $sessions[ $time ][ $track ] = $session_id
+ // Use 0 as the track ID if no tracks exist.
+ $sessions = array();
+ $sessions_query = new WP_Query( $query_args );
+
+ foreach ( $sessions_query->posts as $session ) {
+ $time = absint( get_post_meta( $session->ID, '_wcpt_session_time', true ) );
+ $terms = get_the_terms( $session->ID, 'wcb_track' );
+
+ if ( ! isset( $sessions[ $time ] ) ) {
+ $sessions[ $time ] = array();
+ }
+
+ if ( empty( $terms ) ) {
+ $sessions[ $time ][0] = $session->ID;
+ } else {
+ foreach ( $terms as $track ) {
+ $sessions[ $time ][ $track->term_id ] = $session->ID;
+ }
+ }
+ }
+
+ // Sort all sessions by their key (timestamp).
+ ksort( $sessions );
+
+ return $sessions;
+}
+
+/**
+ * Return an array of columns identified by term ids to be used for schedule table.
+ *
+ * @param array $tracks Array of terms for tracks from get_schedule_tracks().
+ * @param array $sessions Array of sessions from get_schedule_sessions().
+ * @param bool $tracks_explicitly_specified True if tracks were explicitly specified in the shortcode,
+ * false otherwise.
+ *
+ * @return array Array of columns identified by term ids.
+ */
+function get_schedule_columns( $tracks, $sessions, $tracks_explicitly_specified ) {
+ $columns = array();
+
+ // Use tracks to form the columns.
+ if ( $tracks ) {
+ foreach ( $tracks as $track ) {
+ $columns[ $track->term_id ] = $track->term_id;
+ }
+ } else {
+ $columns[0] = 0;
+ }
+
+ // Remove empty columns unless tracks have been explicitly specified.
+ if ( ! $tracks_explicitly_specified ) {
+ $used_terms = array();
+
+ foreach ( $sessions as $time => $entry ) {
+ if ( is_array( $entry ) ) {
+ foreach ( $entry as $term_id => $session_id ) {
+ $used_terms[ $term_id ] = $term_id;
+ }
+ }
+ }
+
+ $columns = array_intersect( $columns, $used_terms );
+ unset( $used_terms );
+ }
+
+ return $columns;
+}
+
+/**
+ * Update and preprocess input attributes for [schedule] shortcode.
+ *
+ * @param array $attr Array of attributes from shortcode.
+ *
+ * @return array Array of attributes, after preprocessing.
+ */
+function preprocess_schedule_attributes( $attr ) {
+ $attr = shortcode_atts(
+ array(
+ 'date' => null,
+ 'tracks' => 'all',
+ 'speaker_link' => 'anchor', // anchor|wporg|permalink|none
+ 'session_link' => 'permalink', // permalink|anchor|none
+ ), $attr
+ );
+
+ foreach ( array( 'tracks', 'speaker_link', 'session_link' ) as $key_for_case_sensitive_value ) {
+ $attr[ $key_for_case_sensitive_value ] = strtolower( $attr[ $key_for_case_sensitive_value ] );
+ }
+
+ if ( ! in_array( $attr['speaker_link'], array( 'anchor', 'wporg', 'permalink', 'none' ), true ) ) {
+ $attr['speaker_link'] = 'anchor';
+ }
+
+ if ( ! in_array( $attr['session_link'], array( 'permalink', 'anchor', 'none' ), true ) ) {
+ $attr['session_link'] = 'permalink';
+ }
+
+ return $attr;
+}
+
+/**
+ * Return plain text list of sessions marked as favourite sessions.
+ *
+ * Format of each list item:
+ * Time of session | Session title [by Speaker] | Track name(s).
+ *
+ * @param array $sessions_rev Array of sessions with reversed subarray track_id->session_id.
+ * @param array $fav_sessions_lookup Mapping session _id -> 1 for favourite sessions.
+ *
+ * @return string List of sessions.
+ */
+function generate_plaintext_fav_sessions( $sessions_rev, $fav_sessions_lookup ) {
+ $sessions_text = '';
+
+ // timestamp -> session_id -> track_id.
+ foreach ( $sessions_rev as $timestamp => $sessions_at_time ) {
+ foreach ( $sessions_at_time as $session_id => $track_ids ) {
+ // Skip sessions which are not marked favourite.
+ if ( ! isset( $fav_sessions_lookup[ $session_id ] ) ) {
+ continue;
+ }
+
+ $session = get_post( $session_id );
+ $session_title = apply_filters( 'the_title', $session->post_title );
+ $session_tracks = get_the_terms( $session_id, 'wcb_track' );
+ $session_track_titles = is_array( $session_tracks ) ? implode( ', ', wp_list_pluck( $session_tracks, 'name' ) ) : '';
+
+ $speakers = array();
+ $speakers_ids = array_map( 'absint', (array) get_post_meta( $session_id, '_wcpt_speaker_id' ) );
+ if ( ! empty( $speakers_ids ) ) {
+ $speakers = get_posts( array(
+ 'post_type' => 'wcb_speaker',
+ 'posts_per_page' => - 1,
+ 'post__in' => $speakers_ids,
+ ) );
+ }
+
+ $speakers_names = array();
+ foreach ( $speakers as $speaker ) {
+ $speaker_name = apply_filters( 'the_title', $speaker->post_title );
+ $speakers_names[] = $speaker_name;
+ }
+
+ // Line format: Time of session | Session title [by Speaker] | Track name(s).
+ $sessions_text .= date( get_option( 'time_format' ), $timestamp );
+ $sessions_text .= ' | ';
+ $sessions_text .= $session_title;
+ if ( count( $speakers_names ) > 0 ) {
+ $sessions_text .= _x( ' by ', 'Speaker for the session', 'wordcamporg' ) . implode( ', ', $speakers_names );
+ }
+ $sessions_text .= ' | ';
+ $sessions_text .= $session_track_titles;
+ $sessions_text .= "\n";
+ }
+ }
+
+ return $sessions_text;
+
+}
+
+/**
+ * Return array of dates for which there are sessions in the provided array.
+ *
+ * @param array $sessions Array of sessions from get_schedule_sessions().
+ * @param string $date_format Date format string (same format as php).
+ *
+ * @return array Array of dates for WordCamp formatted according to date_format string.
+ */
+function get_sessions_dates( $sessions, $date_format ) {
+ $session_timestamps = array_keys( $sessions );
+
+ $session_dates = array_map(
+ function( $timestamp ) use ( $date_format ) {
+ return date( $date_format, $timestamp );
+ },
+ $session_timestamps
+ );
+
+ return array_unique( $session_dates );
+}
+
+/**
+ * Return true if any of the sessions from $session_rev is in $fav_session_ids,
+ * false otherwise.
+ *
+ * @param array $fav_session_ids Array with favourite sessions as keys.
+ * @param array $sessions_rev Array of sessions from flip_sessions_subarrays().
+ *
+ * @return bool true if there is any intersection, false otherwise.
+ */
+function includes_fav_session( $fav_session_ids, $sessions_rev ) {
+ foreach ( $sessions_rev as $timestamp => $session_id_subarray ) {
+ foreach ( $session_id_subarray as $session_id => $_ ) {
+ if ( isset( $fav_session_ids[ $session_id ] ) ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Return array of sessions with reverted subarrays, i.e. transformed from
+ * timestamp -> track_id -> session_id into
+ * timestamp -> session_id -> [track_id1, track_id2, ...]
+ *
+ * @param array $sessions An array of sessions from get_schedule_sessions().
+ *
+ * @return array Array with format timestamp -> session_id -> [track_id1, track_id2, ...].
+ */
+function flip_sessions_subarrays( $sessions ) {
+ $sessions_reversed = array();
+
+ foreach ( $sessions as $timestamp => $sessions_at_time ) {
+ foreach ( $sessions_at_time as $track_id => $session_id ) {
+ $sessions_reversed[ $timestamp ][ $session_id ][] = $track_id;
+ }
+ }
+
+ return $sessions_reversed;
+}
+
+/**
+ * Return plain text email message body for sharing favourite sessions email.
+ *
+ * @param string $wordcamp_name WordCamp name to be used in the email.
+ * @param array $fav_sessions_lookup Mapping session _id -> 1 for favourite sessions.
+ *
+ * @return string Plain text body of the email.
+ */
+function generate_email_body( $wordcamp_name, $fav_sessions_lookup ) {
+ $date_format = get_option( 'date_format' );
+ $tracks = get_schedule_tracks( 'all' );
+ $tracks_explicitly_specified = false; // include all tracks in the email.
+ $sessions = get_schedule_sessions( null, $tracks_explicitly_specified, $tracks );
+ $sessions_dates = get_sessions_dates( $sessions, $date_format );
+
+ // Convert timestamp -> track_id -> session_id to timestamp -> session_id -> [track_id1, ...].
+ $sessions_reversed = flip_sessions_subarrays( $sessions );
+
+ $email_message = $wordcamp_name . "\n\n";
+
+ // Create list of sessions for each day.
+ foreach ( $sessions_dates as $current_day ) {
+ // Filter only the sessions for the 'current' day.
+ $sessions_for_current_day = array_filter(
+ $sessions_reversed,
+ function( $date_ ) use ( $current_day, $date_format ) {
+ return date( $date_format, $date_ ) === $current_day;
+ },
+ ARRAY_FILTER_USE_KEY
+ );
+
+ $email_message .= $current_day . "\n";
+
+ // Skip days when there's no session marked as favourite.
+ if ( ! includes_fav_session( $fav_sessions_lookup, $sessions_for_current_day ) ) {
+ $email_message .= "\n";
+ continue;
+ }
+
+ $email_message .= generate_plaintext_fav_sessions( $sessions_for_current_day, $fav_sessions_lookup );
+ $email_message .= "\n\n";
+ }
+
+ return $email_message;
+}
+
+/**
+ * Return true if the email favourite sessions feature should be disabled,
+ * false otherwise.
+ *
+ * Kill switch for sharing schedule over email -- both for REST API endpoint and UI
+ * in [schedule] shortcode.
+ *
+ * @return bool true if email functionality should be disabled, false otherwise.
+ */
+function email_fav_sessions_disabled() {
+ return true; // @todo enable after finish discussing abuse mitigation
+}
+
+/**
+ * Send favourite sessions email to address specified in the REST request.
+ *
+ * REST API handler for 'wc-post-types/v1/email-fav-sessions' endpoint.
+ *
+ * @param WP_REST_Request $request REST API Request object.
+ *
+ * @return WP_REST_Response|WP_Error
+ */
+function send_favourite_sessions_email( WP_REST_Request $request ) {
+ // There's no need to check intention or authorization, since this is meant to be available to
+ // unauthenticated visitors.
+
+ if ( email_fav_sessions_disabled() ) {
+ return new WP_REST_Response(
+ array(
+ 'message' => esc_html__( 'Email functionality disabled.', 'wordcamporg' ),
+ ), 200
+ );
+ }
+
+ $params = $request->get_params();
+ // Input sanitized by REST controller.
+ $email_address = $params['email-address'];
+ $fav_sessions = $params['session-list'];
+
+ // Don't send the email if no sessions were marked as favourite.
+ if ( count( explode( ',', $fav_sessions ) ) === 0 ) {
+ return new WP_Error(
+ 'fav_sessions_no_sessions',
+ esc_html__( 'No sessions selected.', 'wordcamporg' ),
+ array(
+ 'status' => 400,
+ )
+ );
+ }
+
+ $fav_sessions_lookup = array_fill_keys( explode( ',', $fav_sessions ), 1 );
+
+ $wordcamp_name = get_wordcamp_name();
+
+ $headers[] = 'From: ' . $wordcamp_name . ' <do-not-reply@wordcamp.org>';
+ $headers[] = 'Content-Type: text/plain; charset=' . get_bloginfo( 'charset' );
+
+ $subject = sprintf( __( 'My favorite sessions for %s', 'wordcamporg' ), $wordcamp_name );
+ $message = generate_email_body( $wordcamp_name, $fav_sessions_lookup );
+
+ if ( wp_mail( $email_address, $subject, $message, $headers ) ) {
+ return new WP_REST_Response(
+ array(
+ 'message' => esc_html__( 'Email sent successfully to ', 'wordcamporg' ) . " $email_address.",
+ ), 200
+ );
+ }
+
+ // Email was not sent successfully.
+ return new WP_Error(
+ 'fav_sessions_email_failed',
+ esc_html__( 'Favourite sessions email failed.', 'wordcamporg' ),
+ array(
+ 'status' => 500,
+ )
+ );
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswcposttypesincrestapiphp"></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/wc-post-types/inc/rest-api.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/wc-post-types/inc/rest-api.php 2017-12-13 21:58:47 UTC (rev 6267)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/inc/rest-api.php 2017-12-13 22:42:47 UTC (rev 6268)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -6,8 +6,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> namespace WordCamp\Post_Types\REST_API;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use WP_Rest_Server;
+
</ins><span class="cx" style="display: block; padding: 0 10px"> defined( 'WPINC' ) || die();
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require_once( 'favorite-schedule-shortcode.php' );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Add non-sensitive meta fields to the speaker/session REST API endpoints
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -111,9 +115,59 @@
</span><span class="cx" style="display: block; padding: 0 10px"> } // End if().
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> add_action( 'rest_api_init', __NAMESPACE__ . '\register_additional_rest_fields' );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Register route for sending schedule of favourite sessions via e-mail.
+ *
+ * This can be disabled in email_fav_sessions_disabled() from favorite-schedule-shortcode.php.
+ *
+ * @return void
+ */
+function register_fav_sessions_email(){
+ register_rest_route(
+ 'wc-post-types/v1', // REST namespace + API version
+ '/email-fav-sessions/', // URL slug
+ array(
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => 'send_favourite_sessions_email',
+ 'args' => array(
+ 'email-address' => array(
+ 'required' => true,
+ 'validate_callback' => function( $value, $request, $param ) {
+ return is_email( $value );
+ },
+ 'sanitize_callback' => function( $value, $request, $param ) {
+ return sanitize_email( $value );
+ },
+ ),
+
+ 'session-list' => array(
+ 'required' => true,
+ 'validate_callback' => function( $value, $request, $param ) {
+ $session_ids = explode( ',', $value );
+ $session_count = count( $session_ids );
+ for ( $i = 0; $i < $session_count; $i++ ) {
+ if ( ! is_numeric( $session_ids[ $i ] ) ) {
+ return false;
+ }
+ }
+ return true;
+ },
+ 'sanitize_callback' => function( $value, $request, $param ) {
+ $session_ids = explode( ',', $value );
+ return implode( ',', array_filter( $session_ids, 'is_numeric' ) );
+ },
+ ),
+ )
+ )
+ );
+}
+add_action( 'rest_api_init', __NAMESPACE__ . '\register_fav_sessions_email' );
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Link all sessions to the speaker in the `speakers` API endpoint
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * This allows clients to request a speaker and get all their sessions embedded in the response, avoiding
</span></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswcposttypesjsfavouritesessionsjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/js/favourite-sessions.js</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/wc-post-types/js/favourite-sessions.js (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/js/favourite-sessions.js 2017-12-13 22:42:47 UTC (rev 6268)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,167 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+jQuery( document ).ready( function ( $ ) {
+ var FavSessions = {
+ favSessKey: 'favourite_sessions',
+
+ get: function () {
+ var favSessions = JSON.parse( localStorage.getItem( this.favSessKey ) );
+
+ if ( ! favSessions ) {
+ favSessions = {};
+ }
+
+ return favSessions;
+ },
+
+ toggleSession: function ( sessionId ) {
+ var favSessions = this.get();
+
+ if ( favSessions.hasOwnProperty( sessionId ) ) {
+ delete favSessions[ sessionId ];
+ } else {
+ favSessions[ sessionId ] = true;
+ }
+
+ localStorage.setItem( this.favSessKey, JSON.stringify( favSessions ) );
+ },
+ };
+
+ function switchCellAppearance( sessionId ) {
+ // (Un)highlight schedule table cell in case a session is (un)marked as favourite.
+ var sessionSelector = '[data-session-id=\'' + sessionId + '\']';
+ var tdElements = document.querySelectorAll( sessionSelector );
+
+ for ( var i = 0; i < tdElements.length; i ++ ) {
+ tdElements[ i ].classList.toggle( 'wcb-favourite-session' );
+ }
+ }
+
+ function switchEmailFavButton() {
+ var favSessions = FavSessions.get();
+
+ // Display email form only if there are any selected sessions.
+ if ( Object.keys( favSessions ).length > 0 ) {
+ $( '.show-email-form' ).show();
+ } else {
+ $( '.show-email-form' ).hide();
+ }
+ }
+
+ function switchSessionFavourite( sessionId ) {
+ FavSessions.toggleSession( sessionId );
+ switchCellAppearance( sessionId );
+ switchEmailFavButton();
+ }
+
+ function initFavouriteSessions() {
+ var favSessions = FavSessions.get();
+
+ if ( favSessions === {} ) {
+ return;
+ }
+
+ // Highlight favourite sessions in table.
+ var sessionIds = Object.keys( favSessions );
+
+ for ( var i = 0; i < sessionIds.length; i ++ ) {
+ var sessionId = sessionIds[ i ];
+
+ if ( favSessions[ sessionId ] === true ) {
+ switchCellAppearance( sessionId );
+ }
+ }
+
+ switchEmailFavButton();
+ }
+
+ function hideSpinnerShowResult( message ) {
+ var fadeInDelay = 300;
+ $( '.fav-session-email-wait-spinner' ).fadeOut( fadeInDelay );
+
+ setTimeout( function () {
+ $( '.fav-session-email-result' ).html( message );
+ $( '.fav-session-email-result' ).fadeIn();
+ }, fadeInDelay );
+ }
+
+ function hideFormShowSpinner() {
+ var fadeInDelay = 300;
+
+ $( '#fav-session-email-form' ).fadeOut( fadeInDelay );
+
+ setTimeout( function () {
+ $( '.fav-session-email-wait-spinner' ).fadeIn();
+ }, fadeInDelay );
+ }
+
+ $( '.show-email-form' ).click( function ( event ) {
+ event.preventDefault();
+
+ // Slide the slider.
+ $( '.email-form' ).toggleClass( 'fav-session-email-form-hide' ).toggleClass( 'fav-session-email-form-show' );
+
+ // After the animation finishes, activate the form again and hide the previous result.
+ setTimeout( function () {
+ // Clear previous email result.
+ $( '.fav-session-email-result' ).html( '' );
+
+ // Show form div & clear email address.
+ $( '#fav-session-email-form' ).show();
+ $( '#fav-sessions-email-address' ).val( '' );
+ }, 500 );
+
+ return false;
+ } );
+
+ $( '.fav-session-button' ).click( function ( event ) {
+ event.preventDefault();
+
+ var elem = $( this );
+ var sessionId = elem.parent().parent().data( 'session-id' );
+ switchSessionFavourite( sessionId );
+
+ return false;
+ } );
+
+ $( '#fav-sessions-form' ).on( 'submit', function ( event ) {
+ event.preventDefault();
+ hideFormShowSpinner();
+ var favSessions = FavSessions.get();
+ favSessions = Object.keys( favSessions ).toString();
+
+ // Get email from the input.
+ var emailAddress = '';
+ if ( $( '#fav-sessions-email-address' ) ) {
+ emailAddress = $( '#fav-sessions-email-address' ).val();
+ } else {
+ return;
+ }
+
+ // Compile data object.
+ var data = {
+ 'email-address': emailAddress,
+ 'session-list': favSessions,
+ };
+
+ $.ajax( {
+ method: 'POST',
+ url: favSessionsPhpObject.root + 'wc-post-types/v1/email-fav-sessions',
+ data: data,
+ success: function ( response ) {
+ hideSpinnerShowResult( response.message );
+ },
+ fail: function ( response ) {
+ hideSpinnerShowResult( response.message );
+ },
+ error: function ( jqXHR, textStatus, errorThrown ) {
+ if ( textStatus === 'timeout' ) {
+ hideSpinnerShowResult( favSessionsPhpObject.i18n.reqTimeOut );
+ } else {
+ hideSpinnerShowResult( favSessionsPhpObject.i18n.otherError );
+ }
+ },
+ timeout: 5000,
+ } );
+ } );
+
+ initFavouriteSessions();
+} );
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswcposttypeswcposttypesphp"></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/wc-post-types/wc-post-types.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/wc-post-types/wc-post-types.php 2017-12-13 21:58:47 UTC (rev 6267)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wc-post-types/wc-post-types.php 2017-12-13 22:42:47 UTC (rev 6268)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5,6 +5,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> require( 'inc/back-compat.php' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require_once( 'inc/favorite-schedule-shortcode.php' );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> class WordCamp_Post_Types_Plugin {
</span><span class="cx" style="display: block; padding: 0 10px"> protected $wcpt_permalinks;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -513,120 +514,15 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @todo cleanup
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> function shortcode_schedule( $attr, $content ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $attr = shortcode_atts( array(
- 'date' => null,
- 'tracks' => 'all',
- 'speaker_link' => 'anchor', // anchor|wporg|permalink|none
- 'session_link' => 'permalink', // permalink|anchor|none
- ), $attr );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->enqueue_schedule_shortcode_dependencies();
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- foreach ( array( 'tracks', 'speaker_link', 'session_link' ) as $key_for_case_sensitive_value ) {
- $attr[ $key_for_case_sensitive_value ] = strtolower( $attr[ $key_for_case_sensitive_value ] );
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $attr = preprocess_schedule_attributes( $attr );
+ $tracks = get_schedule_tracks( $attr['tracks'] );
+ $tracks_explicitly_specified = 'all' !== $attr['tracks'];
+ $sessions = get_schedule_sessions( $attr['date'], $tracks_explicitly_specified, $tracks );
+ $columns = get_schedule_columns( $tracks, $sessions, $tracks_explicitly_specified );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( ! in_array( $attr['speaker_link'], array( 'anchor', 'wporg', 'permalink', 'none' ) ) )
- $attr['speaker_link'] = 'anchor';
-
- if ( ! in_array( $attr['session_link'], array( 'permalink', 'anchor', 'none' ) ) )
- $attr['session_link'] = 'permalink';
-
- $columns = array();
- $tracks = array();
-
- $query_args = array(
- 'post_type' => 'wcb_session',
- 'posts_per_page' => -1,
- 'meta_query' => array(
- 'relation' => 'AND',
- array(
- 'key' => '_wcpt_session_time',
- 'compare' => 'EXISTS',
- ),
- ),
- );
-
- if ( 'all' == $attr['tracks'] ) {
- // Include all tracks.
- $tracks = get_terms( 'wcb_track' );
- } else {
- // Loop through given tracks and look for terms.
- $terms = array_map( 'trim', explode( ',', $attr['tracks'] ) );
- foreach ( $terms as $term_slug ) {
- $term = get_term_by( 'slug', $term_slug, 'wcb_track' );
- if ( $term )
- $tracks[ $term->term_id ] = $term;
- }
-
- // If tracks were provided, restrict the lookup in WP_Query.
- if ( ! empty( $tracks ) ) {
- $query_args['tax_query'][] = array(
- 'taxonomy' => 'wcb_track',
- 'field' => 'id',
- 'terms' => array_values( wp_list_pluck( $tracks, 'term_id' ) ),
- );
- }
- }
-
- if ( $attr['date'] && strtotime( $attr['date'] ) ) {
- $query_args['meta_query'][] = array(
- 'key' => '_wcpt_session_time',
- 'value' => array(
- strtotime( $attr['date'] ),
- strtotime( $attr['date'] . ' +1 day' ),
- ),
- 'compare' => 'BETWEEN',
- 'type' => 'NUMERIC',
- );
- }
-
- // Use tracks to form the columns.
- if ( $tracks ) {
- foreach ( $tracks as $track )
- $columns[ $track->term_id ] = $track->term_id;
- } else {
- $columns[ 0 ] = 0;
- }
-
- unset( $tracks );
-
- // Loop through all sessions and assign them into the formatted
- // $sessions array: $sessions[ $time ][ $track ] = $session_id
- // Use 0 as the track ID if no tracks exist
-
- $sessions = array();
- $sessions_query = new WP_Query( $query_args );
- foreach ( $sessions_query->posts as $session ) {
- $time = absint( get_post_meta( $session->ID, '_wcpt_session_time', true ) );
- $tracks = get_the_terms( $session->ID, 'wcb_track' );
-
- if ( ! isset( $sessions[ $time ] ) )
- $sessions[ $time ] = array();
-
- if ( empty( $tracks ) ) {
- $sessions[ $time ][ 0 ] = $session->ID;
- } else {
- foreach ( $tracks as $track )
- $sessions[ $time ][ $track->term_id ] = $session->ID;
- }
- }
-
- // Sort all sessions by their key (timestamp).
- ksort( $sessions );
-
- // Remove empty columns unless tracks have been explicitly specified
- if ( 'all' == $attr['tracks'] ) {
- $used_terms = array();
-
- foreach ( $sessions as $time => $entry )
- if ( is_array( $entry ) )
- foreach ( $entry as $term_id => $session_id )
- $used_terms[ $term_id ] = $term_id;
-
- $columns = array_intersect( $columns, $used_terms );
- unset( $used_terms );
- }
-
- $html = '<table class="wcpt-schedule" border="0">';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $html = '<table class="wcpt-schedule" border="0">';
</ins><span class="cx" style="display: block; padding: 0 10px"> $html .= '<thead>';
</span><span class="cx" style="display: block; padding: 0 10px"> $html .= '<tr>';
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -717,6 +613,11 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $classes[] = 'wcpt-session-type-' . $session_type;
</span><span class="cx" style="display: block; padding: 0 10px"> $classes[] = 'wcb-session-' . $session->post_name;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Favourite session star-icon.
+ $content = '<div class="wcb-session-favourite-icon">';
+ $content .= '<a class="fav-session-button"><span class="dashicons dashicons-star-filled"></span></a></div>';
+ $content .= '<div class="wcb-session-cell-content">';
+
</ins><span class="cx" style="display: block; padding: 0 10px"> // Determine the session title
</span><span class="cx" style="display: block; padding: 0 10px"> if ( 'permalink' == $attr['session_link'] && 'session' == $session_type )
</span><span class="cx" style="display: block; padding: 0 10px"> $session_title_html = sprintf( '<a class="wcpt-session-title" href="%s">%s</a>', esc_url( get_permalink( $session->ID ) ), $session_title );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -725,7 +626,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> else
</span><span class="cx" style="display: block; padding: 0 10px"> $session_title_html = sprintf( '<span class="wcpt-session-title">%s</span>', $session_title );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $content = $session_title_html;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $content .= $session_title_html;
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $speakers_names = array();
</span><span class="cx" style="display: block; padding: 0 10px"> foreach ( $speakers as $speaker ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -748,6 +649,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> if ( count( $speakers_names ) )
</span><span class="cx" style="display: block; padding: 0 10px"> $content .= sprintf( ' <span class="wcpt-session-speakers">%s</span>', implode( ', ', $speakers_names ) );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // End of cell-content.
+ $content .= '</div>';
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $columns_clone = $columns;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // If the next element in the table is the same as the current one, use colspan
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -765,7 +669,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $columns_html .= sprintf( '<td colspan="%d" class="%s" data-track-title="%s">%s</td>', $colspan, esc_attr( implode( ' ', $classes ) ), $session_track_titles, $content );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $columns_html .= sprintf( '<td colspan="%d" class="%s" data-track-title="%s" data-session-id="%s">%s</td>', $colspan, esc_attr( implode( ' ', $classes ) ), $session_track_titles, esc_attr( $session->ID ), $content );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $global_session = $colspan == count( $columns ) ? ' global-session' : '';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -779,13 +683,88 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $html .= '</tbody>';
</span><span class="cx" style="display: block; padding: 0 10px"> $html .= '</table>';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $html .= $this->fav_session_email_form();
</ins><span class="cx" style="display: block; padding: 0 10px"> return $html;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Enqueue style and scripts needed for [schedule] shortcode.
+ */
+ function enqueue_schedule_shortcode_dependencies() {
+ wp_enqueue_style( 'dashicons' );
+
+ wp_enqueue_script(
+ 'favourite-sessions',
+ plugin_dir_url( __FILE__ ) . 'js/favourite-sessions.js',
+ array( 'jquery' ),
+ filemtime( plugin_dir_path( __FILE__ ) . 'js/favourite-sessions.js' ),
+ true
+ );
+
+ wp_localize_script(
+ 'favourite-sessions',
+ 'favSessionsPhpObject',
+ array(
+ 'root' => esc_url_raw( rest_url() ),
+ 'i18n' => array(
+ 'reqTimeOut' => esc_html__( 'Sorry, the email request timed out.', 'wordcamporg' ),
+ 'otherError' => esc_html__( 'Sorry, the email request failed.', 'wordcamporg' ),
+ ),
+ )
+ );
+ }
+
+ /**
+ * Return HTML code for email form used to send/share favourite sessions over email.
+ *
+ * Both form and button/link to show/hide the form can be styled using classes email-form
+ * and show-email-form, respectively.
+ *
+ * @return string HTML code that represents the form to send emails and a link to show and hide it.
+ */
+ function fav_session_email_form() {
+ static $email_form_count = 0;
+
+ // Skip email form if it is disabled or it was already added to document.
+ if ( email_fav_sessions_disabled() || $email_form_count !== 0 ) {
+ return '';
+ }
+
+ ob_start();
+ ?>
+
+ <div class="email-form fav-session-email-form-hide">
+ <div id="fav-session-email-form">
+ <?php esc_html_e( 'Send me my favorite sessions:', 'wordcamporg' ); ?>
+
+ <form id="fav-sessions-form">
+ <input type="text" name="email_address" id="fav-sessions-email-address" placeholder="my@email.com" />
+ <input type="submit" value="<?php esc_attr_e( 'Send', 'wordcamporg' ); ?>" />
+ </form>
+ </div>
+ <div class="fav-session-email-wait-spinner"></div>
+ <div class="fav-session-email-result"></div>
+ </div>
+
+ <a class="show-email-form" href="javascript:">
+ <span class="dashicons dashicons-star-filled"></span>
+ <span class="dashicons dashicons-email-alt"></span>
+ </a>
+
+ <?php
+ $email_form = ob_end_flush();
+
+ $email_form_count++;
+
+ return $email_form;
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Returns a speaker's WordPress.org profile url (if username set)
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param $speaker_id int The speaker's post id.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ *
+ * @return NULL|string
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> function get_speaker_wporg_permalink( $speaker_id ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $post = get_post( $speaker_id );
</span></span></pre>
</div>
</div>
</body>
</html>