wenpai.org/wp-content/plugins/o2/modules/to-do/load.php
2024-06-21 23:57:39 +08:00

588 lines
18 KiB
PHP

<?php
/**
* Derived from the P2 Resolved Posts plugin as written by Andrew Nacin and Daniel Bachhuber, with
* contributions from Hugo Baeta and Joey Kudish.
*
* @package o2
* @subpackage o2_ToDos
*/
if ( ! class_exists( 'o2_ToDos' ) ) {
class o2_ToDos extends o2_API_Base {
/**
* Backwards-compatible with P2 Resolved Posts stored information
*/
const taxonomy = 'p2_resolved';
const audit_log_key = 'p2_resolved_log';
const post_actions_key = 'resolvedposts';
function __construct() {
// Taxonomy
add_action( 'init', array( $this, 'register_taxonomy' ) );
// Scripts and styles
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) );
// Actions
add_action( 'o2_templates', array( $this, 'get_templates' ) );
add_action( 'o2_callback_o2_resolved_posts', array( $this, 'callback' ) );
add_action( 'init', array( $this, 'register_post_action_states' ) );
// Widgets
include( dirname( __FILE__ ) . '/inc/class-to-do-widget.php' );
add_action( 'widgets_init', array( $this, 'widgets_init' ) );
// Filters
add_filter( 'o2_filter_post_actions', array( $this, 'add_post_action' ), 15, 2 );
add_filter( 'o2_filter_post_action_html', array( $this, 'wrap_post_action_html' ), 15, 2 );
add_filter( 'o2_post_fragment', array( $this, 'get_post_fragment' ), 10, 2 );
add_filter( 'o2_filter_widget_filters', array( $this, 'filter_widget_filters' ) );
add_filter( 'o2_page_title', array( $this, 'page_title' ) );
add_filter( 'o2_options', array( $this, 'show_front_side_post_box_with_results' ) );
add_filter( 'request', array( $this, 'request' ) );
// Infinite Scroll
add_filter( 'infinite_scroll_ajax_url', array( $this, 'infinite_scroll_ajax_url' ) );
add_filter( 'infinite_scroll_query_args', array( $this, 'infinite_scroll_query_args' ) );
// Polling
add_filter( 'o2_sanitized_query_vars', array( $this, 'polling_sanitized_query_vars' ) );
// Options
$o2_options = get_option( 'o2_options' );
if ( array_key_exists( 'mark_posts_unresolved', $o2_options ) && $o2_options['mark_posts_unresolved'] )
add_filter( 'p2_resolved_posts_mark_new_as_unresolved', '__return_true' );
if ( apply_filters( 'p2_resolved_posts_mark_new_as_unresolved', false ) )
add_action( 'transition_post_status', array( $this, 'mark_new_as_unresolved' ), 10, 3 );
}
function enqueue_scripts() {
wp_enqueue_script( 'o2-extend-to-do-models-audit-log', plugins_url( 'modules/to-do/js/models/audit-log.js', O2__FILE__ ), array( 'o2-models-base' ) );
wp_enqueue_script( 'o2-extend-to-do-collections-audit-logs', plugins_url( 'modules/to-do/js/collections/audit-logs.js', O2__FILE__ ), array( 'o2-extend-to-do-models-audit-log' ) );
wp_enqueue_script( 'o2-extend-to-do-views-audit-log', plugins_url( 'modules/to-do/js/views/audit-log.js', O2__FILE__ ), array( 'o2-extend-to-do-models-audit-log', 'wp-backbone' ) );
wp_enqueue_script( 'o2-extend-to-do-views-extend-post', plugins_url( 'modules/to-do/js/views/extend-post.js', O2__FILE__ ), array( 'o2-cocktail', 'o2-extend-to-do-views-audit-log', 'o2-notifications' ) );
}
function enqueue_style() {
wp_register_style( 'o2-extend-to-do', plugins_url( 'modules/to-do/css/style.css', O2__FILE__ ) );
wp_style_add_data( 'o2-extend-to-do', 'rtl', 'replace' );
wp_enqueue_style( 'o2-extend-to-do' );
}
/**
* Get state object by slug
*
* @param string The state slug
* @return object The state object
*/
public static function get_state( $slug ) {
global $o2_post_action_states;
return $o2_post_action_states[ self::post_actions_key ][ $slug ];
}
/**
* Get the first state object in the carousel
*
* @return object The first state object
*/
public static function get_first_state_slug() {
global $o2_post_action_states;
$keys = array_keys( $o2_post_action_states[ self::post_actions_key ] );
return array_shift( $keys );
}
/**
* Get the last state object in the carousel
*
* @return object The last state object
*/
public static function get_last_state_slug() {
global $o2_post_action_states;
$keys = array_keys( $o2_post_action_states[ self::post_actions_key ] );
return array_pop( $keys );
}
/**
* Get the next state object in the carousel
*
* @param string The state slug
* @return object The next state object
*/
public static function get_next_state_slug( $slug ) {
global $o2_post_action_states;
return $o2_post_action_states[ self::post_actions_key ][ $slug ][ 'nextState' ];
}
/**
* Get the state slugs in the carousel
*
* @return array An ordered array of the state slugs
*/
public static function get_state_slugs() {
$state_slugs = array();
$state_slug = self::get_first_state_slug();
$first_state_slug = $state_slug;
do {
$state_slugs[] = $state_slug;
$state_slug = self::get_next_state_slug( $state_slug );
} while ( $state_slug !== $first_state_slug );
return $state_slugs;
}
/**
* Make this compatible with Infinite Scroll, which we use for loading pages
* @props tmoorewp
*/
function infinite_scroll_ajax_url( $ajax_url ) {
if ( isset( $_GET['resolved'] ) && in_array( $_GET['resolved'], self::get_state_slugs() ) ) {
$ajax_url = add_query_arg( array( 'o2_resolved' => sanitize_key( $_GET['resolved'] ) ), $ajax_url );
}
if ( isset( $_GET['tags'] ) ) {
$ajax_url = add_query_arg( array( 'tags' => sanitize_key( $_GET['tags'] ) ), $ajax_url );
}
return $ajax_url;
}
function infinite_scroll_query_args( $query_args ) {
if ( ! is_array( $query_args ) )
return $query_args;
if ( isset( $_GET['o2_resolved'] ) && in_array( $_GET['o2_resolved'], self::get_state_slugs() ) ) {
$query_args['resolved'] = sanitize_key( $_GET['o2_resolved'] );
}
if ( isset( $_GET['tags'] ) ) {
$query_args['tags'] = sanitize_key( $_GET['tags'] );
}
return $query_args;
}
/**
* Make this compatible with polling
*/
function polling_sanitized_query_vars( $query_vars ) {
if ( isset( $_GET['resolved'] ) && in_array( $_GET['resolved'], self::get_state_slugs() ) ) {
$query_vars['resolved'] = sanitize_key( $_GET['resolved'] );
}
if ( isset( $_GET['tags'] ) ) {
$query_args['tags'] = sanitize_key( $_GET['tags'] );
}
return $query_vars;
}
/**
* Automatically mark new posts as unresolved
*/
function mark_new_as_unresolved( $new_status, $old_status, $post ) {
if ( 'publish' != $new_status || 'publish' == $old_status )
return;
// Only apply to posts, since other types won't have controls to change it
if ( 'post' !== $post->post_type )
return;
// Exclude some post types from automatic marking
if ( ! apply_filters( 'o2_resolved_posts_maybe_mark_new_as_unresolved', true, $post ) )
return;
$new_state_slug = self::get_next_state_slug( self::get_first_state_slug() );
self::set_post_state( $post->ID, $new_state_slug );
}
/**
* Register the resolution taxonomy
*/
public static function register_taxonomy() {
if ( ! taxonomy_exists( self::taxonomy ) ) {
register_taxonomy( self::taxonomy, 'post', array(
'public' => true,
'label' => __( 'o2 To Do', 'o2' ),
'query_var' => 'resolved',
'rewrite' => false,
'show_ui' => false,
) );
}
}
/**
* Set a post state
*
* @param int The post ID
* @param string The state slug, or an empty string to unset the state
*/
public static function set_post_state( $post_id, $term = null ) {
$current_state_slug = self::get_post_state_slug( $post_id );
if ( $current_state_slug == $term ) {
$error = new WP_Error();
$error->add( 'duplicate_resolution_detected', __( 'The post has already been set to that resolution.', 'o2' ) );
return $error;
}
do_action( 'o2_resolved_posts_set_post_state', $post_id, $term );
return wp_set_post_terms( $post_id, $term, self::taxonomy, false );
}
/**
* Get a post state
*
* @param int The post ID
* @return string The post's set state object, or the default first state object
*/
public static function get_post_state_slug( $post_id ) {
$args = array(
'fields' => 'names',
);
$terms = wp_get_post_terms( $post_id, self::taxonomy, $args );
if ( $terms && ! is_wp_error( $terms ) && in_array( $terms[0], self::get_state_slugs() ) )
$state = $terms[0];
else
$state = self::get_first_state_slug();
return $state;
}
/**
* Add state and audit logs for each post to the o2 post fragment
*/
function get_post_fragment( $fragment, $post_id ) {
$post_meta = $fragment['postMeta'];
$post_meta['resolvedPostsPostState'] = self::get_post_state_slug( $post_id );
$audit_logs = get_post_meta( $post_id, self::audit_log_key );
$audit_log_fragments = array();
foreach ( $audit_logs as $audit_log ) {
$audit_log_fragments[] = self::get_audit_log_fragment( $audit_log );;
}
$post_meta['resolvedPostsAuditLogs'] = $audit_log_fragments;
$fragment['postMeta'] = $post_meta;
return $fragment;
}
function filter_widget_filters( $filters ) {
$relative_to_url = is_archive() ? false : home_url();
$filters['filter-resolved.o2'] = array(
'label' => __( 'Done', 'o2' ),
'is_active' => array( $this, 'is_resolved_filter_active' ),
'url' => esc_url( add_query_arg( 'resolved', 'resolved', $relative_to_url ) ),
'priority' => 25,
'css_id' => 'o2-filter-resolved-posts'
);
$filters['filter-unresolved.o2'] = array(
'label' => __( 'To Do', 'o2' ),
'is_active' => array( $this, 'is_unresolved_filter_active' ),
'url' => esc_url( add_query_arg( 'resolved', 'unresolved', $relative_to_url ) ),
'priority' => 20,
'css_id' => 'o2-filter-unresolved-posts'
);
return $filters;
}
function is_resolved_filter_active() {
return ( is_archive() && isset( $_GET['resolved'] ) && $_GET['resolved'] == 'resolved' );
}
function is_unresolved_filter_active() {
return ( is_archive() && isset( $_GET['resolved'] ) && $_GET['resolved'] == 'unresolved' );
}
/**
* Add the audit log template to the o2 templates
*/
function get_templates() {
?>
<script type="html/template" id="tmpl-o2-extend-resolved-posts-audit-log">
{{{ data.avatar }}}
<span class="audit-log-text">
{{ data.log }}<br /><span class="audit-log-date o2-timestamp" data-unixtime="{{ data.timestamp }}"></span>
</span>
</script>
<?php
}
/*
* Register our post action
*/
function register_post_action_states() {
$resolved_post_action_states = array(
'normal' => array(
'name' => __( 'Normal', 'o2' ),
'shortText' => __( 'To-do', 'o2' ),
'title' => __( 'To-do', 'o2' ),
'classes' => array( 'state-normal' ),
'genericon' => 'genericon-dot',
'nextState' => 'unresolved',
'default' => true
),
'unresolved' => array(
'name' => __( 'Unresolved', 'o2' ),
'shortText' => __( 'Mark as done', 'o2' ),
'title' => __( 'Mark as done', 'o2' ),
'classes' => array( 'state-unresolved'),
'genericon' => 'genericon-checkmark',
'nextState' => 'resolved'
),
'resolved' => array(
'name' => __( 'Resolved', 'o2' ),
'shortText' => __( 'Clear to-do', 'o2' ),
'title' => __( 'Clear to-do', 'o2' ),
'classes' => array( 'state-resolved'),
'genericon' => 'genericon-close',
'nextState' => 'normal'
)
);
o2_register_post_action_states( self::post_actions_key, $resolved_post_action_states );
}
/**
* Add a link for changing the post resolution to the o2 post actions
*
* @param array The current o2 post actions
* @return array The filtered o2 post actions
*/
function add_post_action( $actions, $post_ID = false ) {
// Do not show resolved post action on pages - return immediately
if ( is_page() ) {
return $actions;
}
if ( !$post_ID ) {
$post_ID = get_the_ID();
}
// Otherwise, proceed
// First, mark this button as disabled if the user cannot change the state
$button_classes = array( 'o2-resolve-link' );
if ( ! current_user_can( 'edit_post', $post_ID ) ) {
$button_classes[] = 'o2-disabled-action';
}
$post_state_slug = self::get_post_state_slug( $post_ID );
$actions[42] = array(
'action' => self::post_actions_key,
'href' => '#',
'classes' => $button_classes,
'rel' => false,
'initialState' => $post_state_slug
);
return $actions;
}
/**
* Wrap the default action HTML for .o2-resolve-link actions
*/
function wrap_post_action_html( $html, $action ) {
if ( self::post_actions_key === $action[ 'action' ] && ! empty( $html ) ) {
$html = "<span class='o2-resolve-wrap'>{$html}<ul></ul></span>";
}
return $html;
}
/**
* Add an audit log entry to the post's audit logs in meta
*
* @param int The post ID
* @param array Arguments for the audit log
*/
function add_audit_log( $post_id, $args = array() ) {
$defaults = array(
'user_login' => wp_get_current_user()->user_login,
'new_state' => '',
'timestamp' => time(),
);
$args = array_merge( $defaults, $args );
add_post_meta( $post_id, self::audit_log_key, $args );
return $args;
}
/**
* Get an audit log fragment for a single audit log
*
* @param array Arguments for the audit log
* @return array A formatted audit log fragment
*/
function get_audit_log_fragment( $args ) {
$defaults = array(
'user_login' => '',
'new_state' => '',
'timestamp' => time(),
);
$args = array_merge( $defaults, $args );
$fragment = array(
'timestamp' => $args['timestamp'],
);
// Handle removed users
$user = get_user_by( 'login', $args['user_login'] );
if ( $user ) {
$avatar = get_avatar( $user->ID, 32 );
$display_name = $user->display_name;
} else {
$avatar = '';
$display_name = __( 'Someone', 'o2' );
}
if ( ! empty( $args['new_state'] ) ) {
$log = sprintf( __( '%1$s marked this %2$s', 'o2' ), esc_html( $display_name ), esc_html( $args['new_state'] ) );
$log = apply_filters( 'o2_resolved_posts_audit_log_entry', $log, $args );
} else {
$log = sprintf( __( '%s removed resolution', 'o2' ), esc_html( $display_name ) );
}
$fragment['log'] = $log;
$fragment['avatar'] = $avatar;
return $fragment;
}
/**
* Process a post's state change by updating the post state and adding an audit log
*
* @param int The post ID
* @param string The next state slug
*/
function change_post_state( $post_id, $next_state ) {
$success = self::set_post_state( $post_id, $next_state );
if ( is_wp_error( $success ) )
return $success;
// Record change to audit log
$args = array(
'new_state' => $next_state,
);
$args = self::add_audit_log( $post_id, $args );
// Update post for polling
o2_Fragment::bump_post( $post_id );
return $args;
}
/**
* Parse the request if it's a request for our unresolved posts
*/
function request( $qvs ) {
if ( !empty( $_GET['tags'] ) || !empty( $_GET['post_tag'] ) ) {
$filter_tags = ( !empty( $_GET['tags'] ) ) ? $_GET['tags'] : $_GET['post_tag'];
$filter_tags = (array)explode( ',', $filter_tags );
foreach( (array)$filter_tags as $filter_tag ) {
$filter_tag = sanitize_key( $filter_tag );
$new_tax_query = array(
'taxonomy' => 'post_tag',
);
if ( 0 === strpos( $filter_tag, '-') )
$new_tax_query['operator'] = 'NOT IN';
$filter_tag = trim( $filter_tag, '-' );
if ( is_numeric( $filter_tag ) )
$new_tax_query['field'] = 'ID';
else
$new_tax_query['field'] = 'slug';
$new_tax_query['terms'] = $filter_tag;
$qvs['tax_query'][] = $new_tax_query;
}
}
return $qvs;
}
/**
* Main AJAX callback
*
* Determine if a post's next state is valid, set it, and return the audit log fragment as success, or
* an error message as failure
*
* @param object The post fragment object
*/
function callback( $post_data ) {
if ( ! property_exists( $post_data, 'postID' ) || ! property_exists( $post_data, 'nextState' ) )
self::die_failure( 'invalid_message', __( 'Insufficient information provided.', 'o2' ) );
$post = get_post( absint( $post_data->postID ) );
if ( ! $post )
self::die_failure( 'post_not_found', __( 'Post not found.', 'o2' ) );
if ( ! current_user_can( 'edit_post', $post->ID ) )
self::die_failure( 'cannot_edit_post_resolution', __( 'You are not allowed to edit this post resolution', 'o2' ) );
if ( in_array( $post_data->nextState, self::get_state_slugs() ) )
$next_state = sanitize_key( $post_data->nextState );
else
self::die_failure( 'invalid_post_resolution', __( 'Invalid post resolution.', 'o2' ) );
$success = self::change_post_state( $post->ID, $next_state );
if ( is_wp_error( $success ) )
self::die_failure( 'cannot_set_post_resolution', __( 'Unable to set post resolution.', 'o2' ) );
else
$audit_log = self::get_audit_log_fragment( $success );
$post_terms = o2_Fragment::get_post_terms( $post->ID );
$retval = array(
'currentState' => $next_state,
'auditLog' => $audit_log,
);
self::die_success( $retval );
}
function widgets_init() {
register_widget( 'o2_ToDos_Widget' );
}
function page_title( $page_title ) {
global $wp_query;
$resolved_query = get_query_var( 'resolved' );
if ( ! empty( $resolved_query ) && is_scalar( $resolved_query ) ) {
$resolved_query = strip_tags( wp_kses_no_null( trim( $resolved_query ) ) );
$page_title = is_tag() && $page_title ? $page_title . " | " : '';
if ( 'unresolved' === $resolved_query ) {
$page_title .= sprintf( _x( 'Posts Marked To Do (%d)', 'resolved/unresolved posts', 'o2' ), $wp_query->found_posts );
} else if ( 'resolved' === $resolved_query ) {
$page_title .= sprintf( _x( 'Posts Marked Done (%d)', 'resolved/unresolved posts', 'o2' ), $wp_query->found_posts );
} else {
$page_title .= sprintf( _x( '%s Posts (%d)', 'resolved/unresolved posts', 'o2' ), $resolved_query, $wp_query->found_posts );
}
}
return $page_title;
}
/*
* Enable the front side posting box to appropriate users when the resolved query is included
*/
function show_front_side_post_box_with_results( $options ) {
$resolved_query = get_query_var( 'resolved' );
if ( ! empty( $resolved_query ) ) {
$options['options']['showFrontSidePostBox'] = ( is_user_logged_in() && current_user_can( 'publish_posts' ) );
}
return $options;
}
} }
function o2_todos() {
$o2_options = get_option( 'o2_options' );
if ( ! empty ( $o2_options['enable_resolved_posts'] ) )
new o2_ToDos();
}
add_action( 'o2_loaded', 'o2_todos' );