Instantly share code, notes, and snippets.

Embed
What would you like to do?
Functionality pieces for the NextDraft build
<?php
/**
* AJAX call for parsing HTML
*
*/
public function parse_html() {
// get content from textarea
$text = $_POST['content'];
$announce = !empty( $_POST['announce'] ) ? $_POST['announce'] : '';
$current = !empty( $_POST['current'] ) ? $_POST['current'] : time();
$title = !empty( $_POST['title'] ) ? $_POST['title'] : '';
$nonce = $_POST['nonce'];
// set up return array for ajax responses
$ret = array();
check_ajax_referer( 'nd_parse_nonce', 'nonce' );
// run check for empty content
if( empty( $text ) ) {
$ret['success'] = false;
$ret['errcode'] = 'NO_CONTENT';
$ret['message'] = __( 'No content has been entered', 'ndp' );
echo json_encode($ret);
die();
}
// run content creation function
$sections = $this->parse_content( trim( stripslashes( $text ) ) );
// run check for failed creation
if( $sections == false ) {
$ret['success'] = false;
$ret['errcode'] = 'NO_SECTIONS';
$ret['message'] = __( 'The content could not determine the section breaks. Check your HTML.', 'ndp' );
echo json_encode($ret);
die();
}
// create parent item
$parent = $this->create_parent( $current, $title, $announce );
// run check for failed creation
if( $parent == false ) {
$ret['success'] = false;
$ret['errcode'] = 'NO_PARENT';
$ret['message'] = __( 'The parent item could not be created', 'ndp' );
echo json_encode($ret);
die();
}
// create child items
$children = $this->create_children( $sections, $parent, $current );
if( $children == false ) {
$ret['success'] = false;
$ret['errcode'] = 'NO_CHILDREN';
$ret['message'] = __( 'The sections could not be created', 'ndp' );
echo json_encode($ret);
die();
}
// everything got made. tell the world!
if( $children !== false ) {
$ret['success'] = true;
$ret['message'] = __( 'Success! Your content has been generated.', 'ndp' );
echo json_encode($ret);
die();
}
// we've reached the end, and nothing worked....
$ret['success'] = false;
$ret['errcode'] = 'UNKNOWN';
$ret['message'] = __( 'There was an unknown error.', 'ndp' );
echo json_encode($ret);
die();
}
/**
* create child items
*/
public function create_children( $sections, $parent, $current ) {
// bail on nothing coming through
if ( !isset ( $sections ) || empty ( $sections ) )
return false;
if ( !isset ( $parent ) || empty ( $parent ) )
return false;
// set array for title cleanup
$num_array = array(
'<h2>1. ',
'<h2>2. ',
'<h2>3. ',
'<h2>4. ',
'<h2>5. ',
'<h2>6. ',
'<h2>7. ',
'<h2>8. ',
'<h2>9. ',
'<h2>10. ',
'</h2>'
);
$count = 0;
foreach ($sections as $section) :
$count++;
// get section variables
$section_title = $section['title'];
$section_title = str_replace( $num_array, '', $section_title );
// do some cleanup on the text
$original = array( '<div class="newsletter_section">', '</div>', '', '', '' );
$replaced = array( '', '', '"', '"', '\'' );
$parsed_text = trim( $section['content'] );
$section_text = str_replace( $original, $replaced, $parsed_text );
// add integer adjust to keep unique
$adjustment = $count * 15;
$current = $current + $adjustment;
// convert date
$issue_stamp = $current;
$dateconvert = new DateTime('@'.$issue_stamp);
$issue_local = $dateconvert->format('Y-m-d H:i:s');
//
$dateconvert->setTimezone(new DateTimeZone('America/Los_Angeles'));
$issue_gmt = $dateconvert->format('Y-m-d H:i:s');
// create variable array for post insertion
$post_args = array(
'post_type' => 'nd-archives',
'post_title' => wp_strip_all_tags( $section_title ),
'post_parent' => intval( $parent ),
'post_content' => $section_text,
'post_status' => 'publish',
'menu_order' => intval( $count ),
'post_date' => $issue_local,
'post_date_gmt' => $issue_gmt,
'comment_status' => 'closed'
);
// create the new post
$section_id = wp_insert_post( $post_args, true );
if ( is_wp_error( $section_id ) )
return false;
if ( !is_wp_error( $section_id ) ) {
// create array of meta
$meta['import'] = true;
$meta['stamp'] = $issue_stamp;
update_post_meta( $section_id, '_nd_section_order', $count );
update_post_meta( $section_id, '_nd_type', 'section' );
update_post_meta( $section_id, '_nd_meta', $meta );
}
endforeach;
return true;
}
/**
* create parent item
*/
public function create_parent( $current, $title, $announce ) {
// create title and post slug
$parent_title = date('F jS', $current )." - The Day's Most Fascinating News";
$parent_slug = date('Ymd', $current );
// get WP formatted dates
$dateconvert = new DateTime('@'.$current);
$parent_local = $dateconvert->format('Y-m-d H:i:s');
$dateconvert->setTimezone(new DateTimeZone('America/Los_Angeles'));
$parent_gmt = $dateconvert->format('Y-m-d H:i:s');
// create parent
$parent_args = array(
'post_type' => 'nd-archives',
'post_title' => wp_strip_all_tags( $parent_title ),
'post_name' => 'n'.$parent_slug,
'post_content' => '',
'post_status' => 'publish',
'post_date' => $parent_local,
'post_date_gmt' => $parent_gmt,
'comment_status' => 'closed',
'menu_order' => 0
);
// create the new parent item
$parent = wp_insert_post( $parent_args, true );
if ( is_wp_error( $parent ) )
return false;
if ( !is_wp_error( $parent ) ) {
// create array of meta
$meta['title'] = wp_strip_all_tags($title);
$meta['unique'] = $this->unique_id();
$meta['import'] = true;
$chimp['status'] = 'pending';
update_post_meta( $parent, '_nd_type', 'parent' );
update_post_meta( $parent, '_nd_chimp_meta', 'pending' );
update_post_meta( $parent, '_nd_stamp', $current );
update_post_meta( $parent, '_nd_meta', $meta );
if ( !empty( $announce ) )
update_post_meta( $parent, '_nd_announce', $announce );
}
// add a timestamp to the DB for last updates
update_option( 'nd_last_updated', time() );
return $parent;
}
/**
* function for taking pasted HTML and running through PHPDom
*/
public function parse_content( $text ) {
// bail on nothing coming through
if ( !isset ( $text ) || empty ( $text ) )
return false;
libxml_use_internal_errors(true);
$text = str_replace( '&', '&amp;', $text );
$text = mb_convert_encoding( $text, 'utf-8', mb_detect_encoding( $text) );
$text = mb_convert_encoding( $text, 'html-entities', 'utf-8' );
$html = new DomDocument();
$html->loadHTML( $text );
$finder = new DomXPath( $html );
$class = 'newsletter_section';
$nodes = $finder->query( "//div[contains(@class, '$class' )]" );
if ( libxml_get_errors() )
return false;
// bail if no array was made
if( !isset( $nodes ) || empty( $nodes ) )
return false;
// create an array of my content
foreach( $nodes as $element) {
$title = $html->saveHTML( $finder->query( './h2', $element )->item(0) );
$rawtext = $html->saveHTML( $element );
$content = str_replace( $title, '', $rawtext );
$content = trim( str_replace( array( '<br>', '<br />' ) , '', $content ) );
$item['title'] = $title;
$item['content'] = $content;
$sections[] = $item;
}
// bail if no array was made
if( !isset( $sections ) || !is_array( $sections ) )
return false;
return $sections;
}
<?php
/**
* set args for RSS feed
*/
public function custom_rss( $query ) {
if ( is_admin() )
return;
// exclude child items on ND archive
if ( is_post_type_archive('nd-archives') && $query->is_main_query() && is_feed() ) {
$query->query_vars['post_parent'] = 0;
$query->query_vars['meta_key'] = '_nd_stamp';
$query->query_vars['order'] = 'DESC';
$query->query_vars['orderby'] = 'meta_value_num';
return;
}
}
/**
* add NextDraft logo to archive RSS feed
*/
public function rss_logo() {
global $wp_query;
// bail on non RSS
if ( !isset( $wp_query->query_vars['feed'] ) )
return;
// check for our custom post type
if ( $wp_query->query_vars['post_type'] != 'nd-archives' )
return;
$logo = '';
$logo .= '<image>';
$logo .= '<title>NextDraft</title>';
$logo .= '<url>'. plugins_url( 'lib/img/rss-logo.png', __FILE__ ) . '</url>';
$logo .= '<link>'. home_url() .'/current/</link>';
$logo .= '<width>144</width>';
$logo .= '<height>43</height>';
$logo .= '<description>NextDraft</description>';
$logo .= '</image>';
echo $logo;
}
/**
* change main RSS title
*/
public function rss_main_title( $title ) {
global $wp_query;
// bail on non RSS
if ( !isset( $wp_query->query_vars['feed'] ) )
return $title;
// check for our custom post type
if ( $wp_query->query_vars['post_type'] == 'nd-archives' )
$title = ': The Day\'s Most Fascinating News';
return $title;
}
/**
* change RSS title to match first child element
*/
public function rss_item_title( $title ) {
global $post;
if ( $post->post_type !== 'nd-archives' )
return $title;
if ( $post->post_parent != 0 )
return $title;
$args = array(
'fields' => 'ids',
'post_type' => 'nd-archives',
'post_parent' => $post->ID,
'posts_per_page' => 1,
'meta_key' => '_nd_section_order',
'order' => 'ASC',
'orderby' => 'meta_value_num',
'suppress_filters' => false
);
$blurbs = get_posts( $args );
if ( ! $blurbs )
return $title;
$first = $blurbs[0];
$title = get_post_field( 'post_title', $first, 'raw' );
$title = trim( $title );
return $title;
}
public function rss_description( $excerpt ) {
global $post;
if ( $post->post_type !== 'nd-archives' )
return $excerpt;
if ( $post->post_parent != 0 )
return $excerpt;
$stamp = get_post_meta( $post->ID, '_nd_stamp', true );
$clean = date( 'F jS, Y', $stamp );
$excerpt = 'The Day\'s Most Fascinating News for '.$clean;
return $excerpt;
}
/**
* display child elements on RSS feed
*/
public function rss_content( $content ) {
global $post;
if ( $post->post_type !== 'nd-archives' )
return $content;
if ( $post->post_parent != 0 )
return $content;
$parent_id = $post->ID;
$args = array(
'fields' => 'ids',
'post_type' => 'nd-archives',
'post_parent' => $parent_id,
'nopaging' => true,
'meta_key' => '_nd_section_order',
'order' => 'ASC',
'orderby' => 'meta_value_num',
'suppress_filters' => false
);
$blurbs = get_posts( $args );
if ( ! $blurbs )
return $content;
// get optional announcement
$announce = get_post_meta( $parent_id, '_nd_announce', true );
$display = '';
$i = 1;
foreach ( $blurbs as $blurb_id ):
$title = get_post_field( 'post_title', $blurb_id, 'raw' );
$text = get_post_field( 'post_content', $blurb_id, 'raw' );
$link = get_permalink( $blurb_id );
$display .= '<div>';
$display .= '<h3><a href="'.$link.'">'.$i.'. '.$title.'</a></h3>';
if ( !empty( $announce ) && $i == 1 )
$display .= '<p class="announcement"><em>'.$announce.'</em></p>';
$display .= apply_filters( 'the_content', $text );
$display .= '</div>';
// add link to home page after 3rd blurb
if ( $i == 3 ) :
$display .= '<div>';
$display .= '<p><a href="'.home_url().'">Get the NextDraft App or Newsletter Here</a></p>';
$display .= '</div>';
endif;
$i++;
endforeach;
return $content.$display;
}
<?php
/**
* fire template_redirect to JSON content
*/
public function json_feeds() {
// none of this should happen on RSS or admin
if ( is_admin() || is_feed() )
return;
global $wp_query;
// bail if we dont have the 'pagename' included
if ( ! isset( $wp_query->query_vars['name'] ) )
return;
// shoot the full archives if we're looking for that
if ( $wp_query->query_vars['name'] == 'archives-json' || $wp_query->query_vars['name'] == 'newsletter_list-json' ) {
$this->archive_output();
exit();
}
// bail on non nd-archives file
if ( !isset( $wp_query->query_vars['nd-archives'] ) )
return;
// bail if we do have the post type included as just a json URL (bad URL)
if ( isset( $wp_query->query_vars['name'] ) && $wp_query->query_vars['name'] == 'json' )
return;
// do a quick check to ensure we have a json extension
$query_base = $wp_query->query_vars['name'];
// check the slug to make sure it's a parent.
$base_check = preg_match( '/^n20/', $query_base );
if ( !$base_check )
return;
// check slug to make sure we are calling json
$name_check = preg_match( '/-json/', $query_base );
if ( !$name_check )
return;
$query_name = str_replace( '-json', '', $query_base );
// passed all our checks, we're gold
if ( isset( $wp_query->query_vars['post_type'] ) && $wp_query->query_vars['post_type'] == 'nd-archives' ) {
$this->single_output( $query_name );
exit;
}
return;
}
/**
* output JSON data for archive page
*/
public function archive_output() {
header( 'HTTP/1.1 200' );
header( 'Content-type: application/json; charset=utf-8' );
$data = $this->archive_data();
$data = str_replace('\\/', '/', json_encode( $data ) );
echo $data;
}
/**
* output JSON data for single issue page
*/
public function single_output( $query_name ) {
header( 'HTTP/1.1 200' );
header( 'Content-type: application/json; charset=utf-8' );
$data = $this->single_data( $query_name );
$data = str_replace('\\/', '/', json_encode( $data ) );
echo $data;
}
/**
* create our endpoints
*
*/
public function json_endpoint() {
// register our "json" endpoint
add_rewrite_endpoint( '.json', EP_NONE );
}
/**
* add JSON into our vars
*/
public function json_vars( $vars ) {
$vars[] = 'json';
return $vars;
}
<?php
/**
* get the sharing setup for each section
*
*/
public function mailchimp_sharing( $section_id ) {
// make sure I got a section ID
if ( empty( $section_id ) || intval( $section_id ) != $section_id )
return;
// get the title and clean it up
$title = get_post_field( 'post_title', $section_id, 'raw' );
$text = urlencode( 'NextDraft: '. trim($title).'' );
// get the shortlink
$short = wp_get_shortlink( $section_id );
$faceb = '<a href="http://www.facebook.com/sharer/sharer.php?u='.$short.'">';
$faceb .= '<img src="'.plugins_url( 'lib/img/email-fb.png', __FILE__ ).'" >';
$faceb .= '</a>';
$twitr = '<a href="https://twitter.com/intent/tweet?source=webclient&text='.$text.$short.'">';
$twitr .= '<img src="'.plugins_url( 'lib/img/email-tw.png', __FILE__ ).'" >';
$twitr .= '</a>';
$share = '<p style="text-align:right;">'.$twitr.'&nbsp;&nbsp;&nbsp;'.$faceb.'</p>';
return $share;
}
/**
* get the individual sections in the MailChimp HTML format
*
*/
public function mailchimp_sections( $parent_id, $section_id ) {
// make sure I got a section ID
if ( empty( $section_id ) || intval( $section_id ) != $section_id )
return;
// grab variables
$announce = get_post_meta( $parent_id, '_nd_announce', true );
$raw_title = get_post_field( 'post_title', $section_id, 'raw' );
$raw_words = get_post_field( 'post_content', $section_id, 'raw' );
$item_count = get_post_meta( $section_id, '_nd_section_order', true );
// add the optional announcement for the first item if present
$raw_words = !empty( $announce ) && $item_count == 1 ? '<i>'.$announce.'</i><br><br>'.$raw_words : $raw_words;
// clean up and tweak
$title = '<h2>'.$item_count.'. '.$raw_title.'</h2>';
$words = mb_convert_encoding( $raw_words, 'HTML-ENTITIES' );
$merge = $title.'<br>'.$words;
return $merge;
}
/**
* clean up the content for the text version of the newsletter
*
*/
public function mailchimp_text_clean( $raw_words ) {
// first, get it in HTML
$raw_words = wpautop( $raw_words );
// do a double replace for plus signs to get a line break
$raw_words = str_replace( "+", "+--", $raw_words );
$raw_words = str_replace( "+", "\n", $raw_words );
// get rid of whitespace
$raw_words = trim( $raw_words );
// do some voodoo to make the links nicer and avoid stripping
$raw_words = str_replace( array( '<a href="', '">' ), array( '[ ', ' ] ' ) , $raw_words );
// now strip everything
$raw_words = strip_tags( $raw_words );
return $raw_words;
}
/**
* get the individual sections in the MailChimp text format
*
*/
public function mailchimp_text_sections( $parent_id, $section_id ) {
// make sure I got a section ID
if ( empty( $section_id ) || intval( $section_id ) != $section_id )
return;
// grab variables
$announce = get_post_meta( $parent_id, '_nd_announce', true );
$raw_title = get_post_field( 'post_title', $section_id, 'raw' );
$item_count = get_post_meta( $section_id, '_nd_section_order', true );
// get the content and start getting wild with it.
$raw_words = get_post_field( 'post_content', $section_id, 'raw' );
$srp_words = $this->mailchimp_text_clean( $raw_words );
// add the optional announcement for the first item if present
$content = !empty( $announce ) && $item_count == 1 ? $announce."\n".$srp_words : $srp_words;
// clean up and tweak
$title = $item_count.'. '.$raw_title;
$section = '';
$section .= $title;
$section .= "\n";
$section .= "\n";
$section .= $content;
$section .= "\n";
return $section;
}
/**
* generate the text email for MailChimp
*
*/
public function mailchimp_text( $parent_id ) {
$sections = $this->get_section_ids( $parent_id );
if ( empty( $sections ) )
return;
// generate my raw text
$raw_text = '';
foreach ( $sections as $section_id ):
$content = $this->mailchimp_text_sections( $parent_id, $section_id );
// bail if no content returned
if ( empty( $content ) )
continue;
// construct section block
$raw_text .= $content;
$raw_text .= "\n";
$raw_text .= "\n";
endforeach;
// add the sign off
$raw_text .= 'Thanks'."\n".'Dave Pell'."\n".'Managing Editor, Internet';
return $raw_text;
}
/**
* generate the HTML setup for MailChimp
*
*/
public function mailchimp_html( $parent_id ) {
$sections = $this->get_section_ids( $parent_id );
if ( empty( $sections ) )
return;
// generate my raw HTML
$raw_html = '';
$raw_html .= '<div class="newsletter">';
foreach ( $sections as $section_id ):
$content = $this->mailchimp_sections( $parent_id, $section_id );
$social = $this->mailchimp_sharing( $section_id );
// bail if no content returned
if ( empty( $content ) )
continue;
// do all the cleanup to get it in MailChimp format.
$content = preg_replace( '/^[ \t]*[\r\n]+/m', '', $content );
$content = str_replace( '+', '<br><br>+', $content );
$content = mb_convert_encoding( $content, 'HTML-ENTITIES' );
$content = trim( $content );
// construct section block
$raw_html .= '<div class="newsletter_section">';
$raw_html .= $content;
$raw_html .= $social;
$raw_html .= '</div>';
$raw_html .= '<br><br>';
endforeach;
// add the sign off
$raw_html .= '<p>Thanks,<br><br>Dave Pell<br>Managing Editor, Internet</p>';
$raw_html .= '</div>'; // end newsletter wrap
return $raw_html;
}
/**
* process the mailchimp creation
*/
public function mailchimp_submit() {
// get our values via $_POST
$parent_id = $_POST['post_id'];
$nonce = $_POST['nonce'];
check_ajax_referer( 'nd_mailchimp_nonce', 'nonce' );
// set up return array for ajax responses
$ret = array();
// check permissions first
if ( ! current_user_can( 'manage_options' ) ) {
$ret['success'] = false;
$ret['errcode'] = 'NO_PERMISSION';
$ret['message'] = __( 'You do not have access to this function. ', 'ndp' );
echo json_encode($ret);
die();
}
// check to make sure we got a post ID
if( !isset( $parent_id ) || empty( $parent_id ) ) {
$ret['success'] = false;
$ret['errcode'] = 'NO_POST_ID';
$ret['message'] = __( 'No post ID was provided.', 'ndp' );
echo json_encode($ret);
die();
}
// make sure our post ID was actually a number
if( intval( $parent_id ) != $parent_id ) {
$ret['success'] = false;
$ret['errcode'] = 'ID_NOT_VALID';
$ret['message'] = __( 'The post ID provided was not valid', 'ndp' );
echo json_encode($ret);
die();
}
// get all my HTML
$html_data = $this->mailchimp_html( $parent_id );
$text_data = $this->mailchimp_text( $parent_id );
if ( empty( $html_data ) ) {
$ret['success'] = false;
$ret['errcode'] = 'NO_HTML';
$ret['message'] = __( 'No HTML was generated.', 'ndp' );
echo json_encode($ret);
die();
}
if ( empty( $text_data ) ) {
$ret['success'] = false;
$ret['errcode'] = 'NO_TEXT';
$ret['message'] = __( 'No text was generated.', 'ndp' );
echo json_encode($ret);
die();
}
// get some metadata from the parent item
$item_meta = get_post_meta( $parent_id, '_nd_meta', true );
$fallback = date('F jS', time() )." - The Day's Most Fascinating News";
$subject = isset( $item_meta['title'] ) && !empty( $item_meta['title'] ) ? $item_meta['title'] : $fallback;
// grab our settings data
$mcdata = get_option('nd-mailchimp' );
if( !isset( $mcdata ) || empty( $mcdata ) ) {
$ret['success'] = false;
$ret['errcode'] = 'NO_MC_DATA';
$ret['message'] = __( 'The MailChimp settings have not been configured.', 'ndp' );
echo json_encode($ret);
die();
}
$apikey = isset( $mcdata['api-key'] ) ? $mcdata['api-key'] : '';
$listid = isset( $mcdata['list-id'] ) ? $mcdata['list-id'] : '';
$template = isset( $mcdata['template'] ) ? $mcdata['template'] : '';
$fromname = isset( $mcdata['from-name'] ) ? $mcdata['from-name'] : '';
$fromemail = isset( $mcdata['from-email'] ) ? $mcdata['from-email'] : '';
// check for empty stuff
if( empty( $apikey ) || empty( $listid ) || empty( $template ) || empty( $fromname ) || empty( $fromemail ) ) {
$ret['success'] = false;
$ret['errcode'] = 'NO_API_KEY';
$ret['message'] = __( 'No API key was provided.', 'ndp' );
echo json_encode($ret);
die();
}
// build Mailchimp query
$options = array(
'list_id' => $listid,
'subject' => $subject,
'from_email' => $fromemail,
'from_name' => $fromname,
'template_id' => $template,
'tracking' => array(
'opens' => true,
'html_clicks' => true,
'text_clicks' => false
),
'title' => $subject
);
$content = array(
'html_bodyContent' => $html_data,
'text' => $text_data
);
// pull in our MailChimp class file
require_once('lib/MCAPI.class.php');
$api = new MCAPI($apikey);
$data = $api->campaignCreate( 'regular', $options, $content );
// start our data testing
if( !isset( $data ) || empty( $data ) ) {
$ret['success'] = false;
$ret['message'] = __( 'MailChimp did not return any data.', 'ndp' );
echo json_encode($ret);
die();
}
if( is_wp_error( $data ) ) {
$ret['success'] = false;
$ret['message'] = __( 'There was an error with your request. Please try again.', 'ndp' );
echo json_encode($ret);
die();
}
// now process the actual return
if( isset( $data->errorCode ) ) {
$ret['success'] = false;
$ret['errcode'] = $data->errorCode;
$ret['message'] = $data->errorMessage;
echo json_encode($ret);
die();
}
if( !isset( $data->errorCode ) ) {
// update the parent item postmeta
$chimp['id'] = $data;
$chimp['status'] = 'complete';
$chimp['process'] = time();
update_post_meta( $parent_id, '_nd_chimp_meta', $chimp );
// build the send button for cloning
$button = '<td class="column-mc-email-test column-mc-item">';
$button .= '<span class="mc-email-test mc-icon-item" data-campaign="'.$data.'">'.__( 'Send Test Email', 'ndp' ).'</span>';
$button .= '<img class="ajax-process" src="'.plugins_url( 'lib/img/wpspin-light.gif', __FILE__ ).'" >';
$button .= '</td>';
$ret['success'] = true;
$ret['mail_id'] = $data;
$ret['pending'] = $parent_id;
$ret['button'] = $button;
$ret['process'] = date( 'M jS Y', time() );
$ret['message'] = __( 'The newsletter has been sent to MailChimp for editing.', 'ndp' );
echo json_encode($ret);
die();
}
// unknown error
$ret['success'] = false;
$ret['message'] = __( 'An unknown error occured. Please try again later.', 'ndp' );
echo json_encode($ret);
die();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment