<?php
/*
Plugin Name: BracketPress
Description: Run and score a tournament bracket pool.
Author: Scott Hack and Nick Temple
Author URI: http://www.bracketpress.com
Version: 1.0.5
*/
/*
BracketPress, Copyright (C)2013 Nick Temple, Scott Hack
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/**
* Based on bbPress singleton model
*/
if (@constant('BRACKETPRESS_DEBUG')) {
error_reporting(E_ALL &~ E_NOTICE &~ E_STRICT);
ini_set("display_errors", 1);
if (!defined('WP_DEBUG')) {
define('WP_DEBUG', true);
}
}
// Security Exit if accessed directly
if ( !defined( 'ABSPATH' ) ) exit;
// prevent hard to find errors because of outdated PHP
if ( version_compare( PHP_VERSION, '5.2', '<' ) ) {
// Thanks for this Yoast!
if ( is_admin() && ( !defined( 'DOING_AJAX' ) || !DOING_AJAX ) ) {
require_once ABSPATH . '/wp-admin/includes/plugin.php';
deactivate_plugins( __FILE__ );
wp_die( __('BracketPress requires PHP 5.2 or higher, as does WordPress 3.2 and higher. The plugin has now disabled itself.', 'bracketpress' ) );
} else {
return;
}
}
if ( !class_exists( 'BracketPress' ) ) :
final class BracketPress {
/** @var BracketPress */
static $instance;
/** @var array */
var $options;
// Globals
var $file;
var $basename;
var $plugin_dir;
var $plugin_url;
var $includes_dir;
var $includes_url;
var $themes_dir;
var $themes_url;
var $bracket_url;
var $bracket_slug;
var $edit_id;
var $bracket_title;
var $default_shortcode;
// The curent post query
var $post;
/** @var BracketPressMatchList */
var $matchlist;
// Generated content to place in the post
var $content;
/** @var BracketPressAdmin */
var $admin;
/** @var array array of the current bracket*/
var $selections;
/**
* Main BracketPress Instance
*
* Ensures that only one instance exists in memory at any one
* time. Also prevents needing to define globals all over the place.
*
* @return BracketPress The one true BracketPress
*/
public static function instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new BracketPress();
self::$instance->setup_globals();
self::$instance->includes();
self::$instance->setup_actions();
if (is_admin()) {
include(self::$instance->includes_dir . 'admin/admin.php');
self::$instance->admin = BracketPressAdmin::instance();
self::$instance->setup_admin_actions(); // For ajax
}
}
return self::$instance;
}
private function __construct() { /* Do nothing here */ }
/**
* Get the full table name of a bracketpress table in the database.
* match, bracket and teams are currently supported.
*
* @param $name
* @return string
*/
function getTable($name) {
global $wpdb;
return $wpdb->prefix . "bracketpress_$name";
}
/**
* Get an option from the database
*
* @param $key
* @param null $default
* @return $option
*/
function get_option($key, $default = null) {
// print_r($this->options);
if (isset($this->options[$key]))
return $this->options[$key];
else
return $default;
}
/**
* Set an option and save it.
* @param $key
* @param $value
*/
function update_option($key, $value) {
$this->options[$key] = $value;
update_option('bracketpress_options', $this->options);
}
/**
* Create any one-time globals, passing through filters
* so you can move them around if necessary.
*/
private function setup_globals() {
// Options list from setings page stored locally as an
// associative array
// access with bracketpress()->get_option and
// bracketpress()->update_option()
$this->options = get_option('bracketpress_options');
/** Paths *************************************************************/
// Setup some base path and URL information
$this->file = __FILE__;
$this->basename = apply_filters( 'bracketpress_plugin_basename', plugin_basename( $this->file ) );
$this->plugin_dir = apply_filters( 'bracketpress_plugin_dir_path', plugin_dir_path( $this->file ) );
$this->plugin_url = apply_filters( 'bracketpress_plugin_dir_url', plugin_dir_url ( $this->file ) );
// Includes
$this->includes_dir = apply_filters( 'bracketpress_includes_dir', trailingslashit( $this->plugin_dir . 'includes' ) );
$this->includes_url = apply_filters( 'bracketpress_includes_url', trailingslashit( $this->plugin_url . 'includes' ) );
// Templates
$this->themes_dir = apply_filters( 'bracketpress_themes_dir', trailingslashit( $this->plugin_dir . 'templates' ) );
$this->themes_url = apply_filters( 'bracketpress_themes_url', trailingslashit( $this->plugin_url . 'templates' ) );
$this->bracket_slug = apply_filters( 'bracketpress_bracket_slug', 'brackets' );
$this->bracket_url = apply_filters( 'bracketpress_bracket_slug', trailingslashit( $this->plugin_url . $this->bracket_slug ) );
$this->edit_id = apply_filters( 'bracketpress_bracket_edit_id', 'edit' );
$this->bracket_title = apply_filters( 'bracketpress_default_bracket_title', 'Bracket' );
$this->default_shortcode = apply_filters( 'bracketpress_default_shortcode', '[bracketpress_display_bracket]' );
}
private function includes() {
require($this->plugin_dir . 'bracketpress-config.php');
require_once( $this->plugin_dir . 'includes/core/actions.php' );
// Include required library files
require_once( $this->plugin_dir . 'lib/functions.php' );
require_once( $this->plugin_dir . 'lib/bracketpress-shortcodes.php');
require_once( $this->plugin_dir . 'lib/bracketpress-widgets.php');
require_once( $this->plugin_dir . 'lib/ajax.php' );
require_once( $this->includes_dir . 'models/queries.php');
require_once( $this->includes_dir . 'models/match.php');
}
/**
* Add a class action to the system, usinh default
* call stack
*
* @param $actions
* @param int $priority
*/
private function add_actions($actions, $priority = 10) {
foreach ($actions as $instance_action => $hook) {
add_action( $hook, array( $this, $instance_action ), $priority );
}
}
private function setup_actions() {
$actions = array(
'register_posts' => 'bracketpress_init',
'route' => 'bracketpress_route',
'generate_rewrite_rules' => 'bracketpress_generate_rewrite_rules',
'add_rewrite_tags' => 'bracketpress_init'
);
add_action( 'wp_login', array( $this, 'on_signin' ), 10, 2 );
register_activation_hook(__FILE__, array($this, 'activate'));
register_deactivation_hook(__FILE__, array($this, 'deactivate'));
$this->add_actions($actions);
}
/**
* Setup admin actions.
* http://codex.wordpress.org/AJAX_in_Plugins
* @todo hook into bracketpress action system
*/
private function setup_admin_actions() {
add_action('wp_ajax_bracketpress', array($this, 'handle_frontend_ajax'));
add_action('wp_ajax_bracketpress', array($this, 'handle_nopriv_frontend_ajax'));
}
function handle_frontend_ajax() {
ob_clean();
bracketpress_do_ajax();
die(); //?per codex. Is it right to die?
}
function activate() {
require_once($this->plugin_dir . 'install/install.php');
$installer = new BracketPressInstaller();
$installer->update_tables();
// Create a master bracket for this user
// if it hasn't been set
if (! $this->get_option('master_id')) {
// We don't have a master bracket
// Create one for the admin, if the admin doesn't have a bracket
$this->create_bracket_post();
// Create the Master Bracket
$master_id = $this->create_bracket_post(null, 'MASTER BRACKET', true); // can be a second bracket
// Note: if you don't want to display the bracket, then remove the shortcode
$this->update_option('master_id', $master_id);
}
if (! $this->get_option('leaderboard_id')) {
$post = array(
'post_type' => 'page',
'post_title' => 'BracketPress Leaderboard',
'post_content' => '[bracketpress_all_brackets]',
'post_status' => 'pending'
);
$this->update_option('leaderboard_id', wp_insert_post($post));
}
if (! $this->get_option('edit_id')) {
$post = array(
'post_type' => 'page',
'post_title' => 'My Brackets',
'post_content' => '[bracketpress_edit]',
'post_status' => 'pending'
);
$this->update_option('edit_id', wp_insert_post($post));
}
flush_rewrite_rules();
}
function deactivate() { }
function register_posts() {
$labels = array(
'name' => __( 'Brackets' ),
'singular_name' => __( 'Bracket' ),
'add_new' => __( 'Add New' ),
'add_new_item' => __( 'Add New Bracket' ),
'edit_item' => __( 'Edit Bracket' ),
'new_item' => __( 'New Bracket' ),
'view_item' => __( 'View Bracket' ),
'search_items' => __( 'Search Brackets' ),
'not_found' => __( 'No brackets found' ),
'not_found_in_trash' => __( 'No brackets found in Trash' ),
'parent_item_colon' => '',
);
$args = array(
'labels' => $labels,
'public' => true,
'rewrite' => array( 'slug' => $this->bracket_slug ),
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => 5,
//'register_meta_box_cb' => 'hmls_add_custom_metaboxes',
//'taxonomies' => array('post_tag', 'category'),
'supports' => array(
'title',
'editor',
'excerpt',
'thumbnail',
'custom-field',
'tags',
'comments',
'author'
),
);
register_post_type( 'brackets', $args );
add_filter('manage_brackets_posts_columns', array($this, 'change_cols'));
add_action('manage_brackets_posts_custom_column', array($this, 'custom_columns'), 10, 2);
add_filter('manage_edit-brackets_sortable_columns', array($this, 'sortable_columns'));
add_filter('request', array($this, 'handle_custom_sorting'));
}
function change_cols($cols) {
$cols = array(
'cb' => '<input type="checkbox" />',
'title' => 'Bracket Title',
'author' => 'User',
'score' => 'Score',
'date' => 'Date'
);
return $cols;
}
function custom_columns($column, $post_id) {
switch($column) {
case 'score':
$score = get_post_meta($post_id, 'score', true);
if (! $score) {
print "Unscored";
} else {
print $score;
}
break;
}
}
function sortable_columns() {
return array('score' => 'score', 'author' => 'author');
}
function handle_custom_sorting($vars) {
if (isset($vars['orderby']) && 'score' == $vars['orderby']) {
$vars = array_merge($vars, array(
'meta_key' => 'score',
'orderby' => 'meta_value_num',
)
);
}
return $vars;
}
/**
* bracketpress rewrite rules as necessary
*/
function add_rewrite_tags() {
add_rewrite_tag( '%%' . $this->edit_id . '%%', '([1]{1,})' ); // Edit Page tag
}
function generate_rewrite_rules( $wp_rewrite ) {
}
/**
* When someone signs in, create their bracket.
*
* @param $username
* @param $user
*/
function on_signin($username, $user) {
$this->create_bracket_post($user->ID);
}
/**
* Create a default bracket post. The bracket matches are created later, when viewed.
*
* @param null $author_id (or current author)
* @param null $title (or default title)
* @param bool $allow_multiple Create an additional post if one exists? (defaults to false)
* @return integer $post_id
*/
function create_bracket_post($author_id = null, $title = null, $allow_multiple = false) {
$user = null;
if ($author_id) {
$user = get_user_by('id', $author_id);
} else {
$user = wp_get_current_user();
}
// Don't do anything if we don't have an author
if (! $user) return null;
$author_id = $user->ID;
// Check for multiple posts
$post_id = $this->get_bracket_for_user($author_id);
if ($post_id && ! $allow_multiple) {
// we have one or posts by this user and we don't allow more.
return $post_id;
}
// If we get here, we want to create the post
// Override post title for SEO?
$post_title = $title ? $title : $this->bracket_title . ' ' . $user->data->display_name;
$post_title = apply_filters('bracketpress_bracket_title', $post_title);
//@todo need a excerpt. Maybe created with a plugin?
$post = array(
'post_type' => 'brackets',
'post_title' => $post_title,
'post_content' => $this->default_shortcode,
'post_status' => 'publish',
'post_author' => $author_id,
);
return wp_insert_post($post);
}
/**
* Return the *first* bracket for this user.
* (usually the only bracket)
*
* @param $id
* @return $post_id or null if none found
*/
function get_bracket_for_user($id) {
$query = new WP_Query(array('post_type' => 'brackets', "author" => $id) );
// We're returning the id of the first post found
if ($query->have_posts()) {
$query->next_post();
return $query->post->ID;
}
return null;
}
/**
* Handle scoring.
*/
function get_score($id = null) {
if (!$id) $id = $this->post->ID;
$score = get_post_meta($id, 'score', true);
if ($score == '') $score = "Unscored";
return $score;
}
function reset_score() {
global $wpdb;
$table_match = bracketpress()->getTable('match');
$table_postmeta = $wpdb->prefix . 'postmeta';
$wpdb->query("update $table_match set points_awarded=NULL");
//@todo: constrain delete by post_type = brackets
$wpdb->query("delete from $table_postmeta where meta_key='score'");
}
function score() {
/** @var wpdb $wpdb */
global $wpdb;
// Could to most of this in one or two massive queries.
// If you want to optimize, go right ahead.
ob_start(); // Debug
print_r($this->options);
$table_match = bracketpress()->getTable('match');
$master = $this->get_option('master_id');
$sql = $wpdb->prepare("select * from $table_match where post_id=%d and winner_id > %d", $master, 0);
$winners = $wpdb->get_results($sql);
print "$sql\n";
foreach ($winners as $winner) {
$match = $this->getMatchDetails($winner->match_id);
$sql =$wpdb->prepare("update $table_match set points_awarded=%d where match_id=%d and winner_id = %d", $match->points, $winner->match_id, $winner->winner_id);
$wpdb->query($sql);
print "$sql\n";
$sql =$wpdb->prepare("update $table_match set points_awarded=%d where match_id=%d and winner_id <> %d", 0, $winner->match_id, $winner->winner_id);
$wpdb->query($sql);
print "$sql\n";
}
// Get all the bracket posts
//$table = $wpdb->prefix . 'posts';
$sql = "select post_id, sum(points_awarded) as score from $table_match group by post_id";
print "$sql\n";
$brackets = $wpdb->get_results($sql);
print_r($brackets);
foreach ($brackets as $bracket) {
$old_score = get_post_meta($bracket->post_id, 'score');
update_post_meta($bracket->post_id, 'score', $bracket->score);
do_action('bracketpress_event_updatescore', array('post_id' => $bracket->post_id, 'old_score' => $old_score, 'score' => $bracket->score));
}
$debug = ob_get_clean();
$log = "<pre>\n$debug</pre>";
// print $log;
return $log;
}
/**
* Get the number of points awarded for this match.
*
* make dynamic based on settings, which means
* we have to figure out which round match is in
* based in it's id.
*
* @param $match_ident
* @return stdClass
*/
function getMatchDetails($match_id) {
$match = new stdClass();
$round = BracketPressMatchList::getRound($match_id);
switch($round) {
case 1: $match->points = $this->get_option('points_first_round'); break;
case 2: $match->points = $this->get_option('points_second_round'); break;
case 3: $match->points = $this->get_option('points_third_round'); break;
case 4: $match->points = $this->get_option('points_fourth_round'); break;
case 5: $match->points = $this->get_option('points_fifth_round'); break; // Final 4
case 6: $match->points = $this->get_option('points_sixth_round'); break; // Final game
default: throw new Exception("Match $match_id doesn't exist for round $round");
}
print "Round: $match_id: $round: {$match->points}\n";
return $match;
}
/**
* Set the page content for the display plugin
*
* @param $content
* @return mixed
*/
function setContent($content) {
$this->content = $content;
return $this->getContent();
}
function getContent() {
return apply_filters( 'bracketpress_content', $this->content);
}
/**
* Get the permalink to this match.
*
* @param $post_id
* @param bool $edit true if you want the link to the edit page
* @return string
*/
function get_bracket_permalink($post_id, $edit = false) {
$p = get_post_permalink($post_id);
if ($edit) {
$p = add_query_arg($this->edit_id, 'true', $p);
}
return $p;
}
/**
* BracketPress Core router
*
* The actual page is generated here, and stored in $content.
* The shortcode then just displayes the page
*
* Main Controller routine
*
* @param $post_query WP_Query
*
*/
function route( $post_query) {
static $in_route = false;
// bail if this is a subrequest
if ($in_route) return;
// Bail if in admin
if ( is_admin() ) return;
// Bail if filters are suppressed on this query
if ( true == $post_query->get( 'suppress_filters' ) ) return;
// Bail if $posts_query is not the main loop
if ( ! $post_query->is_main_query() )
return;
// We are only interested in "brackets"
if ($post_query->get('post_type') !== 'brackets')
return;
$in_route = true;
// Get query variables
$posts = $post_query->get_posts();
$in_route = false;
if($post_query->post_count == 1) {
$is_edit = $post_query->get( 'edit' );
$post = array_pop($posts);
$post->combined_score = get_post_meta($post->ID, 'combined_score', true);
$this->post = $post;
if ($is_edit) {
$this->bracket_edit($post);
} else {
$this->bracket_display($post);
}
} else {
if (@constant('BRACKETPRESS_DEBUG')) {
print "<pre>\n";
print_r($posts);
print "</pre>\n";
}
$this->setContent('LIST OF POSTS');
}
}
function is_bracket_owner() {
if ($this->post->post_author == get_current_user_id()) {
return true;
} else {
return false;
}
}
function get_bracket_close_time() {
$close_datetime = $this->get_option('date_brackets_close') . ' ' . $this->get_option('time_brackets_close');
return strtotime($close_datetime);
}
function is_bracket_closed() {
$now = time();
if ($now > $this->get_bracket_close_time())
return true;
else
return false;
}
// Page Display routines
/**
*
* Create the edit bracket page, and store the content
* for display by the shortcode.
*
* @param $post
*/
function bracket_edit($post) {
$this->post = $post;
$this->matchlist = new BracketPressMatchList($post->ID);
if (!$this->is_bracket_owner()) {
$this->bracket_display($post, '<div class="updated"><p>You must be signed in to edit this bracket.</p></div>');
return;
}
$close = $this->get_bracket_close_time();
$date = strftime("%Y-%m-%d %H:%M:%S", $close);
$date_format = get_option( 'date_format' );
$time_format = get_option( 'time_format' );
$date = date($date_format, $close) . " at " . date($time_format, $close);
$datediff = human_time_diff(time(), $close);
$message = "The bracket will close for editing in $datediff on $date.<br>";
if ($post->ID != $this->get_option('master_id'))
if ($this->is_bracket_closed()) {
$this->bracket_display($post, "$message This bracket has been closed for editing. Good Luck!<br>");
return;
}
//@todo move this out to an action that can be overridden
if (isset($_POST['cmd_bracketpress_save'])) {
$post_data = array(
'ID' => $post->ID,
'post_title' => $_POST['post_title'],
'post_excerpt' => $_POST['post_excerpt'],
'combined_score' => $_POST['combined_score'],
);
$post_data = apply_filters('bracketpress_update_bracket', $post_data );
wp_update_post($post_data);
update_post_meta($post_data['ID'], 'combined_score', $post_data['combined_score']);
$this->post->post_title = $post_data['post_title'];
$this->post->post_excerpt = $post_data['post_excerpt'];
$this->post->combined_score = $post_data['combined_score'];
}
if (isset($_POST['cmd_bracketpress_randomize'])) {
$this->matchlist->randomize();
$this->matchlist = new BracketPressMatchList($post->ID, true); // Reload the bracket
do_action('bracketpress_event_randomize');
}
$file = apply_filters( 'bracketpress_template_edit', $this->themes_dir . 'bracket_edit.php' );
ob_start();
include($file);
$output = ob_get_clean();
$this->setContent($output);
}
/**
* Create the display bracket page, and store the content
* for display by the shortcode.
*
* The message (if any) is displayed at the top of the page
* (in the default template)
*
* @param $post
* @param string $message
*/
function bracket_display($post, $message = '') {
$this->post = $post;
$this->matchlist = new BracketPressMatchList($post->ID);
$file = apply_filters( 'bracketpress_template_display', $this->themes_dir . 'bracket_display.php' );
ob_start();
include($file);
$output = ob_get_clean();
$this->setContent($output);
}
}
/**
* The main function responsible for returning the one true BracketPress Instance
* to functions everywhere.
*
* Use this function like you would a global variable, except without needing
* to declare the global.
*/
function bracketpress() {
return bracketpress::instance();
}
// "And now here's something we hope you'll really like!" Run the plugin.
bracketpress();
endif;