ID; } /** * Determines if the current user has permission to edit the current/given post * * @param int $post_id (optional) post ID * @return bool */ function current_user_can( $post_id = 0 ) { return current_user_can( 'edit_post', $this->get_object_id( $post_id ) ); } /** * Whether we should look for and parse task lists * * @return bool */ function parse_task_lists() { return 'content_save_pre' != current_filter(); } /** * Gets the (meta) checked state for the given task list item on the current/given post. * * @param int $task_id * @param int $post_id (optional) * @return array ( checked, checked_by_user_id, checked_timestamp ) */ function get_item_data( $task_id, $post_id = 0 ) { $meta = get_post_meta( $this->get_object_id( $post_id ), "p2_task_{$task_id}", true ); if ( !$meta ) { return array(); } return explode( ':', $meta ); } /** * Sets the (meta) checked state for the given task list item on the current/given post * * @param int $task_id * @param bool $done * @param int $post_id (optional) */ function put_item_data( $task_id, $done = true, $post_id = 0 ) { update_post_meta( $this->get_object_id( $post_id ), "p2_task_{$task_id}", sprintf( '%d:%d:%s', $done, get_current_user_id(), time() ) ); } /** * Deletes the (meta) checked state for the given task list item on the current/given post. * The x/o checked state stored in post_content is not changed * * @param int $task_id * @param int $post_id (optional) */ function delete_item_data( $task_id, $post_id = 0 ) { delete_post_meta( $this->get_object_id( $post_id ), "p2_task_{$task_id}" ); } /** * Gets the post meta keys for each (meta) checked state for all task list items in the current/given post. * * @param int $post_id (optional) * @return array */ function get_all_item_data( $post_id = 0 ) { $meta_keys = get_post_custom_keys( $this->get_object_id( $post_id ) ); if ( !$meta_keys ) { return array(); } $task_id_meta_keys = preg_grep( '/p2_task_\d/', $meta_keys ); if ( !$task_id_meta_keys ) { return array(); } return $task_id_meta_keys; } /** * Deletes all (meta) checked states for the current/given post. * * @param int $post_id (optional) */ function delete_all_item_data( $post_id = 0 ) { $post_id = $this->get_object_id( $post_id ); $task_id_meta_keys = $this->get_all_item_data( $post_id ); foreach ( $task_id_meta_keys as $task_id_meta_key ) { delete_post_meta( $post_id, $task_id_meta_key ); } } /** * Wrapper for renormalizing task list meta into ASCII x's and o's during post edit. * Copies the post meta checked state to x's and o's in post_content */ function edit_post_content( $text, $post_id ) { $task_id_meta_keys = $this->get_all_item_data( $post_id ); if ( !$task_id_meta_keys ) { return $text; } $old_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null; $GLOBALS['post'] = get_post( $post_id ); $text = $this->unparse_list( $text ); $GLOBALS['post'] = $old_post; return $text; } } /** * Parses lists for comments. * * @package P2 */ class P2_Comment_List_Creator extends P2_List_Creator { var $form_action_name = 'p2-comment-task-list'; function __construct() { parent::__construct(); // Parse everything on display add_filter( 'comment_text', array( $this, 'reset_task_list_counter' ), 0 ); add_filter( 'comment_text', array( $this, 'reset_task_list_item_id' ), 0 ); add_filter( 'comment_text', array( $this, 'comment_text' ), 11, 2 ); // Renormalize task list meta into ASCII x's and o's add_filter( 'p2_get_comment_content', array( $this, 'unparse_comment_list' ), 10, 2 ); add_action( 'edit_comment', array( $this, 'delete_all_item_data' ) ); // Parse UL/OL on save add_filter( 'pre_comment_content', array( $this, 'parse_list' ) ); } /** * Parses all lists on display * * Fires on 'comment_text' * * @param string $comment_text * @param object $comment Comment row object * @return string */ function comment_text( $comment_text, $comment = null ) { $old_comment = isset( $GLOBALS['comment'] ) ? $GLOBALS['comment'] : null; $GLOBALS['comment'] = $comment; $comment_text = $this->parse_list( $comment_text ); $GLOBALS['comment'] = $old_comment; return $comment_text; } /** * Returns ID of current/given comment * * @param int $comment_id (optional) comment ID * @return int comment ID */ function get_object_id( $comment_id = 0 ) { $comment = get_comment( $comment_id ); return is_object( $comment ) ? $comment->comment_ID : 0; } /** * Determines if the current user has permission to edit the current/given comment * * @param int $comment_id (optional) comment ID * @return bool */ function current_user_can( $comment_id = 0 ) { return current_user_can( 'edit_comment', $this->get_object_id( $comment_id ) ); } /** * Whether we should look for and parse task lists * * @return bool */ function parse_task_lists() { return 'pre_comment_content' != current_filter(); } /** * Gets the (meta) checked state for the given task list item on the current/given comment. * * @param int $task_id * @param int $comment_id (optional) * @return array ( checked, checked_by_user_id, checked_timestamp ) */ function get_item_data( $task_id, $comment_id = 0 ) { $meta = get_comment_meta( $this->get_object_id( $comment_id ), "p2_task_{$task_id}", true ); if ( !$meta ) { return array(); } return explode( ':', $meta ); } /** * Sets the (meta) checked state for the given task list item on the current/given comment * * @param int $task_id * @param bool $done * @param int $comment_id (optional) */ function put_item_data( $task_id, $done = true, $comment_id = 0 ) { update_comment_meta( $this->get_object_id( $comment_id ), "p2_task_{$task_id}", sprintf( '%d:%d:%s', $done, get_current_user_id(), time() ) ); } /** * Deletes the (meta) checked state for the given task list item on the current/given comment. * The x/o checked state stored in comment_content is not changed * * @param int $task_id * @param int $comment_id (optional) */ function delete_item_data( $task_id, $comment_id = 0 ) { delete_comment_meta( $this->get_object_id( $comment_id ), "p2_task_{$task_id}" ); } /** * Gets the comment meta keys for each (meta) checked state for all task list items in the current/given comment. * * @param int $comment_id (optional) * @return array */ function get_all_item_data( $comment_id = 0 ) { $comment_id = $this->get_object_id( $comment_id ); $meta = get_metadata( 'comment', $comment_id ); if ( !$meta ) { return array(); } $meta_keys = array_keys( $meta ); if ( !$meta_keys ) { return array(); } $task_id_meta_keys = preg_grep( '/p2_task_\d/', $meta_keys ); if ( !$task_id_meta_keys ) { return array(); } return $task_id_meta_keys; } /** * Deletes all (meta) checked states for the current/given comment. * * @param int $comment_id (optional) */ function delete_all_item_data( $comment_id = 0 ) { $comment_id = $this->get_object_id( $comment_id ); $task_id_meta_keys = $this->get_all_item_data( $comment_id ); foreach ( $task_id_meta_keys as $task_id_meta_key ) { delete_comment_meta( $comment_id, $task_id_meta_key ); } } /** * Wrapper for renormalizing task list meta into ASCII x's and o's during comment edit. * Copies the comment meta checked state to x's and o's in comment_content */ function unparse_comment_list( $text, $comment_id ) { if ( !$this->get_all_item_data( $comment_id ) ) { return $text; } $old_comment = isset( $GLOBALS['comment'] ) ? $GLOBALS['comment'] : null; $GLOBALS['comment'] = get_comment( $comment_id ); $text = $this->unparse_list( $text ); $GLOBALS['comment'] = $old_comment; return $text; } } /** * Central class for parsing lists. * * @package P2 */ class P2_List_Creator { /** * @var string name for action parameter of HTML form (not the action attribute, which is always the admin-ajax.php URL) */ var $form_action_name = ''; /** * @var bool Are we currently in a nested list? */ var $doing_recursion = false; var $preserved_texts = array(); function __construct() { // Have we done the CSS/JS already? static $did_header = false; if ( $this->form_action_name ) { // Add form submission handler add_action( "wp_ajax_{$this->form_action_name}", array( $this, 'submit' ) ); } if ( $did_header ) { return; } $did_header = true; add_action( 'wp_head', array( $this, 'css' ) ); add_action( 'wp_head', array( $this, 'js' ) ); if ( !is_admin() ) { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_js' ) ); } } function enqueue_js() { wp_enqueue_script( 'jquery-color' ); } function css() { ?> parse_task_lists() ) { // Parse UL/OL/Task lists return '! # $0 = whole list ^ ([ ]{0,3}) # $1 = nested list space padding ( # $2 = list item marker ([xo]) # $3 = task list item marker | [#*-] # UL/OL item marker ) \s+ # Mandatory whitespace after the list item marker .* # List item $ # EOL (?: # Multiple list items of the same type \n # New line ^ # BOL (?: \1 # Same amount of padding (?(3)(?3)|\2) # Same list item marker | # OR ... \1[ ]{1,} # Increased padding (start of nested list) (?2) # Any list item marker ) \s+ # Mandatory whitespace after the list item marker .* # List item $ # EOL )* !mx'; } return '! # $0 = whole list ^ ([ ]{0,3}) # $1 = number of spaces ([#*-]) # $2 = UL/OL item marker \s+ # Mandatory whitespace after the list item marker .* # List item $ # EOL (?: \n # New line ^ # BOL \1[ ]* # Same or increased amount of padding [xo#*-] # Any list item marker \s+ # Mandatory whitespace after the list item marker .* # List item $ # EOL )* !mx'; } function task_list_regex_to_unparse() { return '! # $0 = whole list ^ ([ ]{0,3}) # $1 = number of spaces ([xo]) # $2 = tasklist item marker \s+ # Mandatory whitespace after the list item marker .* # List item $ # EOL (?: \n # New line ^ # BOL \1[ ]* # Same or increased amount of padding [xo#*-] # Any list item marker \s+ # Mandatory whitespace after the list item marker .* # List item $ # EOL )* !mx'; } function preserve_text( $text ) { global $SyntaxHighlighter; if ( false !== strpos( $text, '[' ) && is_a( $SyntaxHighlighter, 'SyntaxHighlighter' ) && $SyntaxHighlighter->shortcodes ) { $shortcodes_regex = '#\[(' . implode( '|', array_map( 'preg_quote', $SyntaxHighlighter->shortcodes ) ) . ')(?:\s|\]).*\[/\\1\]#s'; $text = preg_replace_callback( $shortcodes_regex, array( $this, 'preserve_text_callback' ), $text ); } if ( false !== strpos( $text, '
).*#s', array( $this, 'preserve_text_callback' ), $text ); } return $text; } function preserve_text_callback( $matches ) { $hash = md5( $matches[0] ); $this->preserved_text[$hash] = $matches[0]; return "[preserved_text $hash /]"; } function restore_text( $text ) { if ( false === strpos( $text, '[preserved_text ' ) ) { return $text; } return preg_replace_callback( '#\[preserved_text (\S+) /\]#', array( $this, 'restore_text_callback' ), $text ); } function restore_text_callback( $matches ) { if ( isset( $this->preserved_text[$matches[1]] ) ) { return $this->preserved_text[$matches[1]]; } return $matches[0]; } /** * Converts * and - into ULs, # into OLs, and x and o into task lists. * * @param string $text Plaintext to parse for lists * @param bool $doing_recursion Are we in a nested list? * @return string HTML */ function parse_list( $text, $doing_recursion = false ) { $text = $this->preserve_text( $text ); $text = preg_replace( '/(\r\n|\r|\n)/', "\n", $text ); // Run our regex through the callback, get the eventual text a few levels down and return it back to P2 here. $old_doing_recursion = $this->doing_recursion; $this->doing_recursion = $doing_recursion; $r = preg_replace_callback( $this->regex_to_parse(), array( $this, '_do_list_callback' ), $text ); $this->doing_recursion = $old_doing_recursion; return $this->restore_text( $r ); } function task_list_counter( $action = 'get' ) { static $id = 0; switch ( $action ) { case 'increment' : $id++; break; case 'reset' : $id = 0; break; } return $id; } function reset_task_list_counter( $content ) { $this->task_list_counter( 'reset' ); return $content; } function task_list_item_id( $action = 'get' ) { static $item_ids = array(); $object_id = $this->get_object_id(); if ( !isset( $item_ids[$object_id] ) ) { $item_ids[$object_id] = 0; } switch ( $action ) { case 'increment' : $item_ids[$object_id]++; break; case 'reset' : $item_ids[$object_id] = 0; break; } return $item_ids[$object_id]; } function reset_task_list_item_id( $content ) { $this->task_list_item_id( 'reset' ); return $content; } /** * Adds UL/OL markup, adds FORM markup for task lists. Calls internal functions for adding LI markup. * * @param array $matches Regex matches from ::parse_list() * @return string HTML */ function _do_list_callback( $matches ) { $id = $this->task_list_counter(); $doing_recursion = $this->doing_recursion; $indent = strlen( $matches[1] ); switch ( $matches[2] ) { case '*' : // UL case '-' : // UL case '#' : // OL if ( '#' == $matches[2] ) { $tag = 'ol'; } else { $tag = 'ul'; } // Easy peasy, lemon squeezy. return "<$tag>\n" . $this->process_list_items( $matches[0], $indent, $matches[2] ) . "\n$tag>\n\n"; break; case 'x' : // Task List case 'o' : // Task List $return = "