public static function poll() { // This is a super lightweight API to get posts and comments from WP // It's intended for use with o2 // @todo Allow requesting a specific post or comment, and a post with all comments // Need to sort things because they're queried separately function o2_date_sort($a, $b) { if ($a->unixtime == $b->unixtime) { return 0; } return $a->unixtime > $b->unixtime ? -1 : 1; } $ok_to_serve_data = true; $ok_to_serve_data = apply_filters('o2_read_api_ok_to_serve_data', $ok_to_serve_data); $data = array(); if ($ok_to_serve_data) { $posts = self::get_posts(); $comments = self::get_comments(); // Clean up posts and comments $data = array(); if (count($posts)) { foreach ($posts as $post) { $data[] = o2_Fragment::get_fragment($post); } } if (count($comments)) { foreach ($comments as $comment) { $data[] = o2_Fragment::get_fragment($comment); } } // Shuffle up and deal usort($data, 'o2_date_sort'); } // Let the client know if the user is logged in or not $is_logged_in = is_user_logged_in(); // Generate an updated nonce (they expire after all, and our "app" may be open for a long time) $new_nonce = wp_create_nonce('o2_nonce'); if ($is_logged_in) { // @todo change to another way, and one that is less costly - see also below // $current_user_id = get_current_user_id(); // update_user_meta( $current_user_id, 'o2_last_poll_gmt', time() ); } $response = array("data" => $data, "newNonce" => $new_nonce, "loggedIn" => $is_logged_in); // Check for unloaded scripts and styles if there are polled posts if (!empty($data)) { // Attach scripts if (isset($_REQUEST['scripts'])) { // Parse and sanitize the script handles already output if (!is_array($_REQUEST['scripts'])) { $_REQUEST['scripts'] = explode(',', $_REQUEST['scripts']); } $initial_scripts = is_array($_REQUEST['scripts']) ? array_map('sanitize_text_field', $_REQUEST['scripts']) : null; if (is_array($initial_scripts)) { global $wp_scripts; if (!$wp_scripts instanceof WP_Scripts) { $wp_scripts = new WP_Scripts(); } // Identify new scripts needed by the polled posts $polled_scripts = array_diff($wp_scripts->done, $initial_scripts); // If new scripts are needed, extract relevant data from $wp_scripts if (!empty($polled_scripts)) { $response['scripts'] = array(); foreach ($polled_scripts as $handle) { // Abort if the handle doesn't match a registered script if (!isset($wp_scripts->registered[$handle])) { continue; } // Provide basic script data $script_data = array('handle' => $handle, 'footer' => is_array($wp_scripts->in_footer) && in_array($handle, $wp_scripts->in_footer), 'extra_data' => $wp_scripts->print_extra_script($handle, false)); // Base source $src = $wp_scripts->registered[$handle]->src; // Take base_url into account if (strpos($src, '//') === 0) { $src = is_ssl() ? 'https:' . $src : 'http:' . $src; } // Deal with root-relative URLs if (strpos($src, '/') === 0) { $src = $wp_scripts->base_url . $src; } if (strpos($src, 'http') !== 0) { $src = $wp_scripts->base_url . $src; } // Version and additional arguments if (null === $wp_scripts->registered[$handle]->ver) { $ver = ''; } else { $ver = $wp_scripts->registered[$handle]->ver ? $wp_scripts->registered[$handle]->ver : $wp_scripts->default_version; } if (isset($wp_scripts->args[$handle])) { $ver = $ver ? $ver . '&' . $wp_scripts->args[$handle] : $wp_scripts->args[$handle]; } // Full script source with version info $script_data['src'] = add_query_arg('ver', $ver, $src); // Add script to data that will be returned to o2 array_push($response['scripts'], $script_data); } } } } // Attach styles if (isset($_REQUEST['styles'])) { // Parse and sanitize the script handles already output if (!is_array($_REQUEST['styles'])) { $_REQUEST['styles'] = explode(',', $_REQUEST['styles']); } // Parse and sanitize the style handles already output $initial_styles = is_array($_REQUEST['styles']) ? array_map('sanitize_text_field', $_REQUEST['styles']) : null; if (is_array($initial_styles)) { global $wp_styles; // Identify new styles needed by the polled posts $polled_styles = array_diff($wp_styles->done, $initial_styles); // If new styles are needed, extract relevant data from $wp_styles if (!empty($polled_styles)) { $response['styles'] = array(); foreach ($polled_styles as $handle) { // Abort if the handle doesn't match a registered stylesheet if (!isset($wp_styles->registered[$handle])) { continue; } // Provide basic style data $styles_data = array('handle' => $handle, 'media' => 'all'); // Base source $src = $wp_styles->registered[$handle]->src; // Take base_url into account if (strpos($src, 'http') !== 0) { $src = $wp_styles->base_url . $src; } // Version and additional arguments if (null === $wp_styles->registered[$handle]->ver) { $ver = ''; } else { $ver = $wp_styles->registered[$handle]->ver ? $wp_styles->registered[$handle]->ver : $wp_styles->default_version; } if (isset($wp_styles->args[$handle])) { $ver = $ver ? $ver . '&' . $wp_styles->args[$handle] : $wp_styles->args[$handle]; } // Full script source with version info $script_data['src'] = add_query_arg('ver', $ver, $src); // @todo Handle parsing conditional comments // Parse requested media context for stylesheet if (isset($wp_styles->registered[$handle]->args)) { $style_data['media'] = esc_attr($wp_styles->registered[$handle]->args); } // Add script to data that will be returned to o2 array_push($response['styles'], $style_data); } } } } } wp_send_json_success($response); }
public function on_ajax() { if (empty($_POST) || !isset($_POST['data'])) { wp_send_json_error(array('errorText' => 'Invalid request. No data. (o2 List Creator)')); } $data = $_POST['data']; $object_ID = isset($data['objectID']) ? $data['objectID'] : ''; $nonce = isset($data['nonce']) ? $data['nonce'] : ''; if (!wp_verify_nonce($nonce, 'o2_nonce')) { wp_send_json_error(array('errorText' => 'Invalid request. Bad nonce. Please refresh the page and try again.')); } $object_type = isset($data['objectType']) ? $data['objectType'] : ''; // Check user's permission to do anything to this object if ('post' == $object_type) { if (!$this->current_user_can_edit_checklist('post', $object_ID)) { wp_send_json_error(array('errorText' => 'Invalid request. The current user cannot edit checklists in this post.')); } } else { if ('comment' == $object_type) { if (!$this->current_user_can_edit_checklist('comment', $object_ID)) { wp_send_json_error(array('errorText' => 'Invalid request. The current user cannot edit checklists in this comment.')); } } else { wp_send_json_error(array('errorText' => 'Invalid request. Unrecognized checklist object type.')); } } $item_hash = isset($data['itemHash']) ? $data['itemHash'] : ''; $item_hash_instance = isset($data['itemHashInstance']) ? $data['itemHashInstance'] : ''; $command = isset($data['command']) ? $data['command'] : ''; $arg1 = isset($data['arg1']) ? $data['arg1'] : ''; $arg2 = isset($data['arg2']) ? $data['arg2'] : ''; $content = $this->get_object_content($object_type, $object_ID); $content = $this->preserve_text($content); $content_array = preg_split('/(\\r\\n|\\r|\\n)/', $content); $updated_content_array = array(); $this->clear_line_hashes(); $line_to_insert_later = ''; foreach ((array) $content_array as $content_line) { // get the line's hash and hash instance $line_hash = self::hash_line($content_line); $instance = $this->add_line_hash($line_hash); // is it the hash and hash instance we're looking for? if ($line_hash == $item_hash && $instance == $item_hash_instance) { if ('delete' == $command) { // nothing to do, just don't append the content_line do_action('o2_checklists_command', $command, $object_type, $object_ID, $content_line); } else { if ('update' == $command) { $arg1 = wp_kses($arg1, $this->allowed_tags_in_tasks); $content_line = preg_replace($this->task_item_regex, '${1}${2}${3}' . $arg1, $content_line); $updated_content_array[] = $content_line; do_action('o2_checklists_command', $command, $object_type, $object_ID, $content_line); } else { if ('add' == $command) { $updated_content_array[] = $content_line; $arg1 = wp_kses($arg1, $this->allowed_tags_in_tasks); $added_content_line = preg_replace($this->task_item_regex, '${1}o${3}' . $arg1, $content_line); $updated_content_array[] = $added_content_line; do_action('o2_checklists_command', $command, $object_type, $object_ID, $added_content_line); } else { if ('check' == $command) { $user_mention = $this->get_current_user_mention(); if ('true' == $arg1) { // check (x) the item $content_line = preg_replace($this->task_item_regex, '${1}x${3}${4}', $content_line); // add their name to the end of the string if it is not there already if ($user_mention != substr($content_line, -strlen($user_mention))) { $content_line .= $user_mention; } do_action('o2_checklists_command', 'check', $object_type, $object_ID, $content_line); } else { // uncheck (o) the item $content_line = preg_replace($this->task_item_regex, '${1}o${3}${4}', $content_line); // if the user mention exists in the string, remove it if (false !== strpos($content_line, $user_mention)) { $content_line = str_replace($user_mention, '', $content_line); } do_action('o2_checklists_command', 'uncheck', $object_type, $object_ID, $content_line); } $updated_content_array[] = $content_line; } else { if ('moveAfter' == $command) { // for this pass, just don't append the content_line - we'll insert it in the correct spot in a second $line_to_insert_later = $content_line; do_action('o2_checklists_command', $command, $object_type, $object_ID, $content_line); } else { if ('moveBefore' == $command) { // for this pass, just don't append the content_line - we'll insert it in the correct spot in a second $line_to_insert_later = $content_line; do_action('o2_checklists_command', $command, $object_type, $object_ID, $content_line); } else { // unrecognized command - do nothing to the line - just pass it through $updated_content_array[] = $content_line; } } } } } } } else { $updated_content_array[] = $content_line; } } if (!empty($line_to_insert_later)) { // insert the moved item in the correct spot // $arg1 contains the hash the item to insert before/after // $arg2 contains the instance $content_array = $updated_content_array; $updated_content_array = array(); $this->clear_line_hashes(); foreach ((array) $content_array as $content_line) { // get the line's hash and hash instance $line_hash = self::hash_line($content_line); $instance = $this->add_line_hash($line_hash); // is it the hash and hash instance we're looking for? if ($line_hash == $arg1 && $instance == $arg2) { if ('moveAfter' == $command) { $updated_content_array[] = $content_line; $updated_content_array[] = $line_to_insert_later; } elseif ('moveBefore' == $command) { $updated_content_array[] = $line_to_insert_later; $updated_content_array[] = $content_line; } } else { // just append and move on $updated_content_array[] = $content_line; } } } $updated_content = implode("\n", $updated_content_array); $updated_content = $this->restore_text($updated_content); // update the object in the database $updated_object = $this->update_object_content($object_type, $object_ID, $updated_content); $fragment = o2_Fragment::get_fragment($updated_object); if (empty($fragment)) { wp_send_json_error(array('errorText' => 'Internal error. Empty fragment. (o2 List Creator)')); } $partial_fragment = array_intersect_key($fragment, $this->key_whitelist); wp_send_json_success($partial_fragment); }
/** * Called from the end of the core commenting process. If we get here * then the comment was posted successfully, so just output it and die. */ public static function comment_success($comment) { do_action('o2_writeapi_comment_created', $comment->comment_ID); self::die_success(o2_Fragment::get_fragment($comment)); }
/** * Called from the end of the core commenting process. If we get here * then the comment was posted successfully, so just output it and die. */ public static function comment_success($comment) { self::die_success(o2_Fragment::get_fragment($comment)); }
/** * Embed JSONified post+comment data into each thread (post) for backbone consumption */ public static function add_json_data($content) { global $post; // password protected post? return immediately (password protected pages are OK) if (!is_page() && !empty($post->post_password)) { return $content; } $conversation = array(); if (is_single() || is_category() || is_archive() || is_author() || is_home() || is_page() || is_search()) { $conversation[] = o2_Fragment::get_fragment($post, array('find-adjacent' => is_single())); // Append the encoded conversation to the content in a hidden script block $content .= "<script class='o2-data' id='o2-data-{$post->ID}' data-post-id='{$post->ID}' type='application/json' style='display:none'>"; $content .= json_encode($conversation); $content .= "</script>\n"; } return $content; }
/** * Returns an entry array for the specified conversation fragment * where fragment_type is 'post' or 'comment' */ public static function get_fragment_from_post($my_post, $args = array()) { remove_filter('the_content', array('o2', 'add_json_data'), 999999); // Avoid infinite loops remove_filter('the_excerpt', array('o2', 'add_json_data'), 999999); // Avoid infinite loops add_filter('home_url', array('o2_Fragment', 'home_url'), 10, 4); $post_ID = $my_post->ID; $post_tags = wp_get_post_tags($post_ID); $permalink = get_permalink($post_ID); // Get a set of classes to be used when displaying. // We force ->post_type because it's not included in core during // AJAX calls (is_admin()) $post_class_array = get_post_class($my_post->post_type, $post_ID); $post_class = !empty($post_class_array); if ($post_class) { $post_class = join(' ', $post_class_array); } // Adjacent post information (prev next) only makes sense for some pages (e.g. is_single) // So, we only fetch adjacent post information if we were asked to (to avoid unnecessary db queries) $prev_post_title = ''; $prev_post_url = ''; $has_prev_post = false; $next_post_title = ''; $next_post_url = ''; $has_next_post = false; if (isset($args['find-adjacent']) && $args['find-adjacent']) { // We can't use the loop because get_fragment_from_post could be called outside of the loop (e.g. by a ajax request from backbone) // Set global so that core functions work as expected global $post; $old_post = $post; $post = $my_post; // temporarily add filters to avoid picking up password protected posts add_filter('get_previous_post_where', array('o2_Fragment', 'get_adjacent_post_where')); add_filter('get_next_post_where', array('o2_Fragment', 'get_adjacent_post_where')); $prev_post = get_previous_post(); $has_prev_post = !empty($prev_post); if ($has_prev_post) { $prev_post_title = $prev_post->post_title; $prev_post_title = apply_filters('the_title', $prev_post_title); $prev_post_url = get_permalink($prev_post->ID); } $next_post = get_next_post(); $has_next_post = !empty($next_post); if ($has_next_post) { $next_post_title = $next_post->post_title; $next_post_title = apply_filters('the_title', $next_post_title); $next_post_url = get_permalink($next_post->ID); } remove_filter('get_previous_post_where', array('o2_Fragment', 'get_adjacent_post_where')); remove_filter('get_next_post_where', array('o2_Fragment', 'get_adjacent_post_where')); // Set global scope back to whatever it was before $post = $old_post; } $raw_post_title = $my_post->post_title; $raw_content = $my_post->post_content; $extended_content = get_extended($raw_content); $title_was_generated_from_content = $raw_post_title == wp_trim_words($raw_content, 5); $post_format = get_post_format($post_ID); if (false === $post_format) { $post_format = 'standard'; } // Filter the title $filtered_post_title = apply_filters('the_title', $raw_post_title); // Handle <!--more--> for same-page toggling global $more; if (!$more) { if (!empty($extended_content['extended'])) { // Set more text if (empty($extended_content['more_text'])) { $extended_content['more_text'] = __('Show full post', 'o2'); } // Set more link $more_text = strip_tags(wp_kses_no_null(trim($extended_content['more_text']))); $more_link = apply_filters('the_content_more_link', "<a href='{$permalink}#more-{$post_ID}' class='more-link'>{$more_text}</a>", $more_text); $extended_content = $extended_content['main'] . "\n\n" . $more_link . "\n<div class='o2-extended-more'>\n\n" . $extended_content['extended'] . "\n\n</div><!--.o2-extended-more-->\n"; // No <!--more--> text } else { $extended_content = $extended_content['main']; } } else { $extended_content = $raw_content; } // When editing the content, SyntaxHighlighter does some magic to decode // HTML entities within [code] blocks. global $SyntaxHighlighter; if (is_a($SyntaxHighlighter, 'SyntaxHighlighter')) { $raw_content = $SyntaxHighlighter->decode_shortcode_contents($raw_content); } // An xpost is already filtered content $xpost_original_permalink = get_post_meta($post_ID, '_xpost_original_permalink', true); if (!empty($xpost_original_permalink)) { $filtered_content = $extended_content; } else { add_filter('the_content', 'o2_Fragment::o2_make_clickable', 9); $filtered_content = apply_filters('the_content', $extended_content); remove_filter('the_content', 'o2_Fragment::o2_make_clickable', 9); } $filtered_content = apply_filters('o2_filtered_content', $filtered_content, $my_post); // Mentions list($mentions, $mention_context) = self::get_mention_data('post', $post_ID, $raw_content); $comment_models = array(); $approved_comments = get_comments(array('post_id' => $post_ID, 'order' => 'ASC', 'status' => 'approve')); $trashed_comments = get_comments(array('post_id' => $post_ID, 'order' => 'ASC', 'status' => 'trash', 'meta_query' => array(array('key' => 'o2_comment_has_children', 'compare' => 'EXISTS')))); /* * For bootstrapping data, we are only interested in approved comments and * trashed comments that have children for now. */ $post_comments = array_merge($approved_comments, $trashed_comments); foreach ((array) $post_comments as $post_comment) { $comment_models[] = o2_Fragment::get_fragment($post_comment); } // Set a post date for previewing drafts $post_date_gmt = $my_post->post_date_gmt; if ('0000-00-00 00:00:00' === $post_date_gmt) { $post_date_gmt = $my_post->post_modified_gmt; } $modified = get_post_meta($post_ID, 'client-modified', true); if (!$modified) { $modified = strtotime($my_post->post_modified_gmt); } $post_actions = o2_get_post_actions($post_ID); $comments_open = comments_open($post_ID); // Password protected page? Check the password before delivering the content $is_page = "page" == get_post_type($post_ID); $is_password_protected = !empty($my_post->post_password); if ($is_page && $is_password_protected && post_password_required($post_ID)) { $post_actions = array(); $comment_models = array(); $comments_open = false; $raw_content = ""; $filtered_content = get_the_password_form($post_ID); } $fragment = array('type' => 'post', 'id' => $post_ID, 'postID' => $post_ID, 'cssClasses' => $post_class, 'parentID' => 0, 'titleRaw' => $raw_post_title, 'titleFiltered' => $filtered_post_title, 'titleWasGeneratedFromContent' => $title_was_generated_from_content, 'contentRaw' => $raw_content, 'contentFiltered' => $filtered_content, 'permalink' => $permalink, 'unixtime' => strtotime($post_date_gmt), 'unixtimeModified' => (int) $modified, 'entryHeaderMeta' => '', 'linkPages' => '', 'footerEntryMeta' => '', 'tagsRaw' => self::get_post_tags_raw($post_tags), 'tagsArray' => self::get_post_tags_array($post_tags), 'loginRedirectURL' => wp_login_url($permalink), 'hasPrevPost' => $has_prev_post, 'prevPostTitle' => $prev_post_title, 'prevPostURL' => $prev_post_url, 'hasNextPost' => $has_next_post, 'nextPostTitle' => $next_post_title, 'nextPostURL' => $next_post_url, 'commentsOpen' => $comments_open, 'is_xpost' => !empty($xpost_original_permalink), 'editURL' => get_edit_post_link($post_ID), 'postActions' => $post_actions, 'comments' => $comment_models, 'postFormat' => $post_format, 'postMeta' => array(), 'postTerms' => self::get_post_terms($post_ID), 'pluginData' => array(), 'isPage' => "page" == get_post_type($post_ID), 'mentions' => $mentions, 'mentionContext' => strip_tags($mention_context), 'isTrashed' => "trash" == get_post_status($post_ID)); // Get author properties (and bootstrap the rest of the model) // e.g. userLogin $post_user_properties = self::get_post_user_properties($my_post); $fragment = array_merge($fragment, $post_user_properties); // @todo - add filter to move theme specific attributes out of here, and allow themes to add more attributes $fragment = apply_filters('o2_post_fragment', $fragment, $post_ID); // as they filter the templates add_filter('the_content', array('o2', 'add_json_data'), 999999); add_filter('the_excerpt', array('o2', 'add_json_data'), 999999); remove_filter('home_url', array('o2_Fragment', 'home_url'), 10, 4); // Force UTF8 to avoid JSON encode issues $fragment = self::to_utf8($fragment); return $fragment; }