Location: PHPKode > scripts > Football Pool > football-pool/classes/class-football-pool-pool.php
<?php
class Football_Pool_Pool {
	public $leagues;
	public $has_bonus_questions = false;
	public $has_matches = false;
	public $has_leagues;
	public $force_lock_time = false;
	private $lock_timestamp;
	private $lock_datestring;
	public $always_show_predictions = 0;
	public $show_avatar = false;
	
	public function __construct() {
		$this->leagues = $this->get_leagues();
		$this->has_leagues = ( Football_Pool_Utils::get_fp_option( 'use_leagues' ) == '1' ) && ( count( $this->leagues ) > 1 );
		
		$this->lock_datestring = Football_Pool_Utils::get_fp_option( 'bonus_question_locktime', '' );
		$this->force_lock_time = 
			( Football_Pool_Utils::get_fp_option( 'stop_time_method_questions', 0, 'int' ) == 1 )
			&& ( $this->lock_datestring != '' );
		if ( $this->force_lock_time ) {
			//$date = DateTime::createFromFormat( 'Y-m-d H:i', $this->lock_datestring );
			$date = new DateTime( Football_Pool_Utils::date_from_gmt( $this->lock_datestring ) );
			$this->lock_timestamp = $date->format( 'U' );
		} else {
			$this->lock_timestamp = 0; // bonus questions have no time threshold
		}
		
		// override hiding of predictions for editable questions?
		$this->always_show_predictions = (int) Football_Pool_Utils::get_fp_option( 'always_show_predictions' );
		
		$matches = new Football_Pool_Matches;
		$this->has_matches = $matches->has_matches;
		
		$this->show_avatar = ( Football_Pool_Utils::get_fp_option( 'show_avatar' ) == 1 );
	}
	
	private function is_toto_result($home, $away, $user_home, $user_away ) {
		return $this->toto( $home, $away ) == $this->toto( $user_home, $user_away );
	}
	
	private function toto( $home, $away ) {
		if ( $home == $away ) return 3;
		if ( $home > $away ) return 1;
		return 2;
	}
	
	public function calc_score( $home, $away, $user_home, $user_away, $joker ) {
		if ( $home == '' || $away == '' )
			return '';
		if ( $user_home == '' || $user_away == '' )
			return 0;
		
		$score = 0;
		// check for toto result
		if ( $this->is_toto_result( $home, $away, $user_home, $user_away ) == true ) {
			// check for exact match
			if ( $home == $user_home && $away == $user_away ) {
				$score = (int) Football_Pool_Utils::get_fp_option( 'fullpoints', FOOTBALLPOOL_FULLPOINTS, 'int' );
			} else {
				$score = (int) Football_Pool_Utils::get_fp_option( 'totopoints', FOOTBALLPOOL_TOTOPOINTS, 'int' );
			}
		}
		// check for goal bonus
		$goal_bonus = Football_Pool_Utils::get_fp_option( 'goalpoints', FOOTBALLPOOL_GOALPOINTS, 'int' );
		if ( $home == $user_home ) $score += $goal_bonus;
		if ( $away == $user_away ) $score += $goal_bonus;
		
		if ( $joker == 1 ) $score *= 2;
		
		return $score;
	}
	
	public function get_users( $league ) {
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;
		
		$sql = "SELECT u.ID AS userId, u.display_name AS userName, u.user_email AS email, ";
		$sql .= ( $this->has_leagues ? "lu.leagueId, " : "" );
		$sql .= "0 AS points, 0 AS full, 0 AS toto, 0 AS bonus FROM {$wpdb->users} u ";
		if ( $this->has_leagues ) {
			$sql .= "INNER JOIN {$prefix}league_users lu 
						ON (u.ID = lu.userId" . ( $league > 1 ? ' AND lu.leagueId = ' . $league : '' ) . ") ";
			$sql .= "INNER JOIN {$prefix}leagues l ON ( lu.leagueId = l.ID ) ";
		} else {
			$sql .= "LEFT OUTER JOIN {$prefix}league_users lu ON (lu.userId = u.ID) ";
			$sql .= "WHERE ( lu.leagueId <> 0 OR lu.leagueId IS NULL ) ";
		}
		$sql .= "ORDER BY userName ASC";
		return $wpdb->get_results( $sql, ARRAY_A );
	}
	
	public function user_is_player( $user_id ) {
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;
		
		if ( $this->has_leagues ) {
			$sql = $wpdb->prepare( "SELECT COUNT(*) FROM {$prefix}league_users lu
									INNER JOIN {$wpdb->users} u ON ( u.ID = lu.userId )
									WHERE u.ID = %d AND lu.leagueId <> 0"
									, $user_id );
		} else {
			$sql = $wpdb->prepare( "SELECT COUNT(*) FROM {$prefix}league_users lu
									RIGHT OUTER JOIN {$wpdb->users} u ON ( u.ID = lu.userId )
									WHERE u.ID = %d AND ( lu.leagueId <> 0 OR lu.leagueId IS NULL )"
									, $user_id );
		}
		
		return ( $wpdb->get_var( $sql ) == 1 );
	}
	
	// returns null if no leagues are available or user does not exist
	public function get_league_for_user( $user_id ) {
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;
		
		if ( $this->has_leagues ) {
			$sql = $wpdb->prepare( "SELECT leagueId FROM {$prefix}league_users WHERE userId = %d", $user_id );
			$league = $wpdb->get_var( $sql );
		} else {
			$league = null;
		}
		
		return (int) $league;
	}
	
	// use league=0 to include all users
	public function get_ranking_from_score_history( $league, $ranking_id = FOOTBALLPOOL_RANKING_DEFAULT,
													$score_date = '', $type = 0 ) {
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;
		$sql = "SELECT u.ID AS userId, u.display_name AS userName, u.user_email AS email, " 
			. ( $this->has_leagues ? "lu.leagueId, " : "" ) 
			. "		COALESCE( MAX( s.totalScore ), 0 ) AS points, 
					COUNT( IF( s.full = 1, 1, NULL ) ) AS full, 
					COUNT( IF( s.toto = 1, 1, NULL ) ) AS toto,
					COUNT( IF( s.type = 1 AND score > 0, 1, NULL ) ) AS bonus 
				FROM {$wpdb->users} u ";
		if ( $this->has_leagues ) {
			$league_switch = ( $league <= FOOTBALLPOOL_LEAGUE_ALL ? '1 = 1 OR' : '' );
			$sql .= "INNER JOIN {$prefix}league_users lu 
						ON (
							u.ID = lu.userId
							AND ( {$league_switch} lu.leagueId = %d )
							) ";
			$sql .= "INNER JOIN {$prefix}leagues l ON ( lu.leagueId = l.ID ) ";
		} else {
			$sql .= "LEFT OUTER JOIN {$prefix}league_users lu ON ( lu.userId = u.ID ) ";
		}
		$sql .= "LEFT OUTER JOIN {$prefix}scorehistory s ON 
					(
						s.userId = u.ID AND s.ranking_id = %d 
						AND ( " . ( $score_date == '' ? '1 = 1 OR' : '' ) . " s.scoreDate <= %s )
						AND ( " . ( $type == 0 ? '1 = 1 OR' : '' ) . " s.type = %d )
					) ";
		$sql .= "WHERE s.ranking_id IS NOT NULL ";
		if ( ! $this->has_leagues ) $sql .= "AND ( leagueId <> 0 OR leagueId IS NULL ) ";
		$sql .= "GROUP BY u.ID
				ORDER BY points DESC, full DESC, toto DESC, bonus DESC, " . ( $this->has_leagues ? "lu.leagueId ASC, " : "" ) . "LOWER( u.display_name ) ASC";
		
		if ( $this->has_leagues )
			return $wpdb->prepare( $sql, $league, $ranking_id, $score_date, $type );
		else
			return $wpdb->prepare( $sql, $ranking_id, $score_date, $type );
	}
	
	public function get_pool_ranking_limited( $league, $num_users, $ranking_id = FOOTBALLPOOL_RANKING_DEFAULT,
												$score_date = '' ) {
		global $wpdb;
		$sql = $this->get_ranking_from_score_history( $league, $ranking_id, $score_date ) . ' LIMIT %d';
		$sql = $wpdb->prepare( $sql, $num_users );
		return $wpdb->get_results( $sql, ARRAY_A );
	}
	
	public function get_pool_ranking( $league, $ranking_id = FOOTBALLPOOL_RANKING_DEFAULT ) {
		$cache_key = 'fp_get_pool_ranking_' . $ranking_id;
		$rows = wp_cache_get( $cache_key );
		
		if ( $rows === false ) {
			global $wpdb;
			$sql = $this->get_ranking_from_score_history( $league, $ranking_id );
			$rows = $wpdb->get_results( $sql, ARRAY_A );
			wp_cache_set( $cache_key, $rows );
		}
		
		return $rows;
	}
	
	public function print_pool_ranking( $league, $user, $ranking_id = FOOTBALLPOOL_RANKING_DEFAULT ) {
		$output = '';
		
		$rows = $this->get_pool_ranking( $league, $ranking_id );
		$ranking = array();
		if ( count( $rows ) > 0 ) {
			// there are results in the database, so get the ranking
			foreach ( $rows as $row ) {
				$ranking[] = $row;
			}
		} else {
			// no results, show a list of users
			$rows = $this->get_users( $league );
			if ( count( $rows ) > 0 ) {
				$output .= '<p>' . __( 'No results yet. Below is a list of all users.', FOOTBALLPOOL_TEXT_DOMAIN ) . '</p>';
				foreach ( $rows as $row ) {
					$ranking[] = $row;
				}
			} else {
				$output .= '<p>'. __( 'No users have registered for this pool (yet).', FOOTBALLPOOL_TEXT_DOMAIN ) . '</p>';
			}
		}
		
		if ( count( $ranking ) > 0 ) {
			$userpage = Football_Pool::get_page_link( 'user' );
			$i = 1;
			
			$output .= '<table class="pool-ranking ranking-page">';
			foreach ( $ranking as $row ) {
				$class = ( $i % 2 != 0 ? 'even' : 'odd' );
				$all_user_view = ( $league == FOOTBALLPOOL_LEAGUE_ALL && $this->has_leagues );
				if ( $all_user_view ) $class .= ' league-' . $row['leagueId'];
				if ( $row['userId'] == $user ) $class .= ' currentuser';
				$output .= sprintf( '<tr class="%s"><td style="width:3em; text-align: right;">%d.</td>
									<td><a href="%s">%s%s</a>%s</td>
									<td>%d</td>
									%s
									</tr>',
								$class,
								$i++,
								esc_url( add_query_arg( array( 'user' => $row['userId'] ), $userpage ) ),
								$this->get_avatar( $row['userId'], 'medium' ),
								$row['userName'],
								Football_Pool::user_name( $row['userId'], 'label' ),
								$row['points'],
								( $all_user_view ? $this->league_image( $row['leagueId'] ) : '' )
							);
				$output .= "\n";
			}
			$output .= '</table>';
		}
		
		return $output;
	}
	
	private function league_image( $id ) {
		if ( $this->has_leagues && ! empty( $this->leagues[$id]['image'] ) ) {
			$img = sprintf( '<td><img src="%sassets/images/site/%s" alt="%s" title="%s" /></td>',
							FOOTBALLPOOL_PLUGIN_URL,
							$this->leagues[$id]['image'],
							$this->leagues[$id]['leagueName'],
							$this->leagues[$id]['leagueName']
						);
		} else {
			$img = '<td></td>';
		}
		return $img;
	}
	
	public function get_leagues( $only_user_defined = false ) {
		$cache_key = 'fp_get_leagues_' . ( $only_user_defined ? 'user_defined' : 'all' );
		$leagues = wp_cache_get( $cache_key );
		
		if ( $leagues === false ) {
			global $wpdb;
			$prefix = FOOTBALLPOOL_DB_PREFIX;
			
			$filter = $only_user_defined ? 'WHERE userDefined=1' : '';
			
			$sql = "SELECT id AS leagueId, name AS leagueName, userDefined, image 
					FROM {$prefix}leagues {$filter} ORDER BY userDefined ASC, name ASC";
			$rows = $wpdb->get_results( $sql, ARRAY_A );
			
			$leagues = array();
			foreach ( $rows as $row ) {
				$leagues[$row['leagueId']] = $row;
			}
			wp_cache_set( $cache_key, $leagues );
		}
		
		return $leagues;
	}
	
	public function get_rankings( $which = 'all' ) {
		$only_user_defined = ( $which == 'user defined' || $which == 'user_defined' );
		$cache_key = 'fp_get_rankings_' . ( $only_user_defined ? 'user_defined' : 'all' );
		$rankings = wp_cache_get( $cache_key );
		
		if ( $rankings === false ) {
			global $wpdb;
			$prefix = FOOTBALLPOOL_DB_PREFIX;
			
			$filter = $only_user_defined ? 'WHERE user_defined = 1' : '';
			
			$sql = "SELECT id, name, user_defined 
					FROM {$prefix}rankings {$filter} ORDER BY user_defined ASC, name ASC";
			$rows = $wpdb->get_results( $sql, ARRAY_A );
			
			$rankings = array();
			foreach ( $rows as $row ) {
				$rankings[$row['id']] = $row;
			}
			wp_cache_set( $cache_key, $rankings );
		}
		
		return $rankings;
	}
	
	public function get_ranking_by_id( $id ) {
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;
		
		$sql = $wpdb->prepare( "SELECT name, user_defined FROM {$prefix}rankings WHERE id = %d", $id );
		return $wpdb->get_row( $sql, ARRAY_A ); // returns null if no ranking found
	}
	
	public function get_ranking_matches( $id ) {
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;
		
		$sql = $wpdb->prepare( "SELECT match_id FROM {$prefix}rankings_matches WHERE ranking_id = %d", $id );
		return $wpdb->get_results( $sql, ARRAY_A ); // returns null if no ranking found
	}
	
	public function get_ranking_questions( $id ) {
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;
		
		$sql = $wpdb->prepare( "SELECT question_id FROM {$prefix}rankings_bonusquestions WHERE ranking_id = %d"
								, $id );
		return $wpdb->get_results( $sql, ARRAY_A ); // returns null if no ranking found
	}
	
	public function league_filter( $league = 0, $select = 'league' ) {
		$output = '';
		
		if ( $this->has_leagues ) {
			$options = array();
			foreach ( $this->leagues as $row ) {
				$options[ $row['leagueId'] ] = $row['leagueName'];
			}
			$output .= Football_Pool_Utils::select( $select, $options, $league );
		}
		
		return $output;
	}
	
	public function league_select( $league = 0, $select = 'league' ) {
		$output = '';
		
		if ( $this->has_leagues ) {
			$output .= sprintf('<select name="%s" id="%s">', $select, $select);
			$output .= '<option value="0"></option>';
			foreach ( $this->leagues as $row ) {
				if ( $row['userDefined'] == 1 ) {
					$output .= sprintf( '<option value="%d"%s>%s</option>',
									$row['leagueId'],
									( $row['leagueId'] == $league ? ' selected="selected"' : '' ),
									$row['leagueName']
								);
				}
			}
			$output .= '</select>';
		}
		return $output;
	}
	
	public function update_league_for_user( $user_id, $new_league_id, $old_league = 'update league' ) {
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;
		
		if ( $old_league == 'no update' ) {
			$sql = $wpdb->prepare( "INSERT INTO {$prefix}league_users ( userId, leagueId ) 
									VALUES ( %d, %d )
									ON DUPLICATE KEY UPDATE leagueId = leagueId", 
									$user_id, $new_league_id
								);
		} else {
			$sql = $wpdb->prepare( "INSERT INTO {$prefix}league_users ( userId, leagueId ) 
									VALUES ( %d, %d )
									ON DUPLICATE KEY UPDATE leagueId = %d", 
									$user_id, $new_league_id, $new_league_id
								);
		}
		$wpdb->query( $sql );
	}
	
	public function get_bonus_questions_for_user( $user_id = 0, $question_ids = array() ) {
		if ( $user_id == 0 ) return false;
		
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;
		
		$ids = '';
		if ( is_array( $question_ids ) && count( $question_ids ) > 0 ) {
			$ids = ' AND q.id IN ( ' . implode( ',', $question_ids ) . ' ) ';
		}
		// also include user answers
		$sql = $wpdb->prepare( "SELECT 
									q.id, q.question, a.answer, 
									q.points, a.points AS userPoints, 
									q.answerBeforeDate AS questionDate, 
									DATE_FORMAT(q.scoreDate,'%%Y-%%m-%%d %%H:%%i') AS scoreDate, 
									DATE_FORMAT(q.answerBeforeDate,'%%Y-%%m-%%d %%H:%%i') AS answerBeforeDate, 
									q.matchNr, a.correct,
									qt.type, qt.options, qt.image, qt.max_answers
								FROM {$prefix}bonusquestions q 
								INNER JOIN {$prefix}bonusquestions_type qt
									ON ( q.id = qt.question_id {$ids})
								LEFT OUTER JOIN {$prefix}bonusquestions_useranswers a
									ON ( a.questionId = q.id AND a.userId = %d )
								ORDER BY q.answerBeforeDate ASC",
							$user_id
						);
		
		$rows = $wpdb->get_results( $sql, ARRAY_A );
		$questions = array();
		
		$this->has_bonus_questions = ( count( $rows ) > 0 );
		
		if ( $this->has_bonus_questions ) {
			$i = 0;
			foreach ( $rows as $row ) {
				$questions[$i] = $row;
				$ts = new DateTime( $row['questionDate'] );
				$ts = $ts->format( 'U' );
				$questions[$i]['question_date'] = $ts;
				$i++;
			}
		}
		
		return $questions;
	}
	
	// returns array of questions
	public function get_bonus_questions() {
		$cache_key = 'fp_bonus_question_info';
		$question_info = wp_cache_get( $cache_key );
		
		if ( $question_info === false ) {
			global $wpdb;
			$prefix = FOOTBALLPOOL_DB_PREFIX;
		
			$sql = "SELECT 
						q.id, q.question, q.answer, q.points, q.answerBeforeDate AS questionDate, 
						DATE_FORMAT(q.scoreDate,'%Y-%m-%d %H:%i') AS scoreDate, 
						DATE_FORMAT(q.answerBeforeDate,'%Y-%m-%d %H:%i') AS answerBeforeDate, q.matchNr,
						qt.type, qt.options, qt.image, qt.max_answers
					FROM {$prefix}bonusquestions q 
					INNER JOIN {$prefix}bonusquestions_type qt
						ON ( q.id = qt.question_id )
					ORDER BY q.answerBeforeDate ASC";
		
			$rows = $wpdb->get_results( $sql );
			$this->has_bonus_questions = ( count( $rows ) > 0 );
			
			$question_info = array();
			foreach ( $rows as $row ) {
				$i = $row->id;
				$question_date = new DateTime( $row->questionDate );
				$ts = $question_date->format( 'U' );
				
				$question_info[$i] = array();
				$question_info[$i]['id'] = $i;
				$question_info[$i]['question'] = $row->question;
				$question_info[$i]['answer'] = $row->answer;
				$question_info[$i]['points'] = $row->points;
				$question_info[$i]['question_date'] = $ts;
				$question_info[$i]['score_date'] = $row->scoreDate;
				$question_info[$i]['answer_before_date'] = $row->answerBeforeDate;
				$question_info[$i]['match_nr'] = $row->matchNr;
				$question_info[$i]['type'] = $row->type;
				$question_info[$i]['options'] = $row->options;
				$question_info[$i]['image'] = $row->image;
				$question_info[$i]['max_answers'] = $row->max_answers;
			}
			
			wp_cache_set( $cache_key, $question_info );
		}
		
		return $question_info;
	}
	
	public function get_bonus_question( $id ) {
		return $this->get_bonus_question_info( $id );
	}
	
	public function get_bonus_question_info( $id ) {
		$info = false;
		$questions = $this->get_bonus_questions();
		if ( is_array( $questions ) && array_key_exists( $id, $questions ) ) {
			$info = $questions[$id];
			$info['bonus_is_editable'] = $this->bonus_is_editable( $info['question_date'] );
		}
		return $info;
	}
	
	private function bonus_question_form_input( $question ) {
		switch ( $question['type'] ) {
			case 2: // multiple 1
				return $this->bonus_question_multiple( $question, 'radio' );
			case 3: // multiple n
				return $this->bonus_question_multiple( $question, 'checkbox' );
			case 1: // text
			default:
				return $this->bonus_question_single( $question );
		}
	}
	
	private function bonus_question_single( $question ) {
		return sprintf( '<input maxlength="200" class="bonus" name="_bonus_%d" type="text" value="%s" />'
						, esc_attr( $question['id'] )
						, esc_attr( $question['answer'] )
				);
	}
	
	// type = radio / checkbox / select
	private function bonus_question_multiple( $question, $type = 'radio' ) {
		$output = '';
		
		if ( $type == 'select' ) {
			// dropdown
			// @todo: bonus question select/dropdown
			$output .= '<select name=""></select>';
		} else {
			// radio or checkbox
			if ( $type == 'checkbox' && $question['max_answers'] > 0 ) {
				// add some javascript for the max number of answers a user may give
				$output .= sprintf( '<script type="text/javascript">jQuery( document ).ready( function() { set_max_answers( %d, %d ); } );</script>'
									, $question['id']
									, $question['max_answers']
							);
			}
			
			$options = explode( ';', $question['options'] );
			$i = 1;
			$output .= '<ul class="multi-select">';
			foreach ( $options as $option ) {
				// strip out any empty options
				if ( str_replace( array( ' ', "\t", "\r", "\n" ), array( '', '', '', '' ), $option ) != '' ) {
					$answer = $question['answer'];
					$js = sprintf( 'onclick="jQuery( \'#_bonus_%d_userinput\' ).val( \'\' )" ', $question['id'] );
					
					if ( $type == 'checkbox' ) {
						$checked = in_array( $option, explode( ';', $answer ) ) ? 'checked="checked" ' : '';
						$brackets = '[]';
						$user_input = '';
					} else {
						// @todo: very hacky (and therefore undocumented) feature of adding a text input
						//        after a radio input
						if ( substr( $option, -2 ) == '[]' ) {
							$js = '';
							
							$option = substr( $option, 0, -2 );
							$len = strlen( $option );
							$checked = substr( $answer, 0, $len ) == $option ? 'checked="checked" ' : '';
							
							$user_input_name = sprintf( '_bonus_%d_userinput', esc_attr( $question['id'] ) );
							$user_input_value = ( $checked ) ? substr( $answer, $len + 1 ) : '';
							$user_input = sprintf( '<span> <input type="text" id="%1$s" name="%1$s" value="%2$s" onclick="jQuery( \'#_bonus_%3$d_%4$d\' ).attr( \'checked\', \'checked\' )" /></span>'
													, $user_input_name
													, $user_input_value
													, $question['id']
													, $i
												);
						} else {
							$user_input = '';
							$checked = ( $answer == $option ) ? 'checked="checked" ' : '';
						}
						$brackets = '';
					}
					
					$user_input_class = ( $user_input != '' ) ? ' class="user-input"' : '';

					$output .= sprintf( '<li><label%9$s><input %8$sid="_bonus_%2$d_%7$d" type="%1$s" name="_bonus_%2$d%5$s" value="%3$s" %4$s/><span class="multi-option"> %3$s</span></label>%6$s</li>'
										, $type
										, esc_attr( $question['id'] )
										, esc_attr( $option )
										, $checked
										, $brackets
										, $user_input
										, $i++
										, $js
										, $user_input_class
								);
				}
			}
			
			$output .= '</ul>';
		}
		
		return $output;
	}
	
	public function print_bonus_question( $question, $nr ) {
		// the question with optional image
		$output = sprintf( '<div class="bonus" id="q%d"><p><span class="nr">%d.</span> %s</p>'
							, $question['id']
							, $nr, $question['question'] 
					);
		if ( $question['image'] != '' ) {
			$output .= sprintf( '<p class="bonus image"><img src="%s" alt="%s" /></p>'
								, $question['image']
								, __( 'photo question', FOOTBALLPOOL_TEXT_DOMAIN )
						);
		}
		
		$lock_time = ( $this->force_lock_time ) ? $this->lock_datestring : $question['answerBeforeDate'];
		// to local time
		$lock_time = Football_Pool_Utils::date_from_gmt( $lock_time );
		
		if ( $this->bonus_is_editable( $question['question_date'] ) ) {
			$output .= sprintf( '<p>%s</p>', $this->bonus_question_form_input( $question ) );
			
			$output .= '<p>';
			
			// remind a player if there is only 1 day left to answer the question.
			$timestamp = ( $this->force_lock_time ? $this->lock_timestamp : $question['questionDate'] );
			if ( ( $timestamp - current_time( 'timestamp' ) ) <= ( 24 * 60 * 60 ) ) {
				$output .= sprintf( '<span class="bonus reminder">%s </span>', __( 'Important:', FOOTBALLPOOL_TEXT_DOMAIN ) );
			}
			$output .= sprintf( '<span class="bonus eindtijd" title="%s">%s ' . $lock_time . '</span>',
							__( 'answer this question before this date', FOOTBALLPOOL_TEXT_DOMAIN ),
							__( 'answer before', FOOTBALLPOOL_TEXT_DOMAIN )
					);
		} else {
			$output .= sprintf( '<p class="bonus" id="bonus-%d">%s: ',
							$question['id'],
							__( 'answer', FOOTBALLPOOL_TEXT_DOMAIN )
					);
			$output .= ( $question['answer'] != '' ? $question['answer'] : '...' );
			$output .= '</p>';
			
			$output .= sprintf( '<p><span class="bonus eindtijd" title="%s">%s %s</span>',
							__( "it's is no longer possible to answer this question, or change your answer", FOOTBALLPOOL_TEXT_DOMAIN ),
							__( 'closed on', FOOTBALLPOOL_TEXT_DOMAIN ),
							$lock_time
					);
		}
		
		$points = $question['points'] == 0 ? __( 'variable', FOOTBALLPOOL_TEXT_DOMAIN ) : $question['points'];
		$output .= sprintf( '<span class="bonus points">%s %s</span></p>'
							, $points
							, __( 'points', FOOTBALLPOOL_TEXT_DOMAIN ) 
					);
		
		$output .= '</div>';
		
		return $output;
	}
	
	// updates the predictions for a submitted prediction form
	public function prediction_form_update() {
		global $current_user;
		get_currentuserinfo();
		
		$user_is_player = $this->user_is_player( $current_user->ID );
		$msg = '';
		
		if ( $current_user->ID != 0 && $user_is_player 
									&& Football_Pool_Utils::post_string( '_fp_action' ) == 'update' ) {
			$nonce = Football_Pool_Utils::post_string( FOOTBALLPOOL_NONCE_FIELD_BLOG );
			$success = ( wp_verify_nonce( $nonce, FOOTBALLPOOL_NONCE_BLOG ) !== false );
			if ( $success ) {
				$success = $this->update_predictions( $current_user->ID );
			}
			if ( $success ) {
				$msg = sprintf( '<p style="errormessage">%s</p>'
								, __( 'Changes saved.', FOOTBALLPOOL_TEXT_DOMAIN )
						);
			} else {
				$msg = sprintf( '<p style="error">%s</p>'
								, __( 'Something went wrong during the save. Check if you are still logged in. If the problems persist, then contact your webmaster.', FOOTBALLPOOL_TEXT_DOMAIN )
						);
			}
		}
		
		return $msg;
	}
	
	private function update_bonus_user_answers( $questions, $answers, $user ) {
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;
		
		foreach ( $questions as $question ) {
			if ( $this->bonus_is_editable( $question['question_date'] ) && $answers[ $question['id'] ] != '') {
				$sql = $wpdb->prepare( "REPLACE INTO {$prefix}bonusquestions_useranswers 
										SET userId = %d,
											questionId = %d,
											answer = %s,
											points = 0",
										$user, $question['id'], $answers[ $question['id'] ]
									);
				$wpdb->query( $sql );
			}
		}
	}
	
	private function update_predictions( $user ) {
		// only allow logged in users and players in the pool to update their predictions
		if ( $user <= 0 || ! $this->user_is_player( $user ) ) return false;
		
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;

		$matches = new Football_Pool_Matches;
		$joker = 0;
		
		// only allow setting of joker if it wasn't used before on a played match
		$sql = $wpdb->prepare( "SELECT m.playDate AS match_timestamp
								FROM {$prefix}predictions p, {$prefix}matches m 
								WHERE p.matchNr = m.nr 
									AND p.hasJoker = 1 AND p.userId = %d" 
								, $user
							);
		$play_date = $wpdb->get_var( $sql );
		if ( $play_date ) {
			$play_date = new DateTime( $play_date );
			$ts = $play_date->format( 'U' );
			if ( $matches->match_is_editable( $ts ) ) {
				$joker = $this->get_joker();
			}
		} else {
			$joker = $this->get_joker();
		}
		
		// get matches
		$rows = $matches->get_info();
		
		// update predictions for all matches
		foreach ( $rows as $row ) {
			$match = $row['nr'];
			$home = Football_Pool_Utils::post_integer( '_home_' . $match, 'NULL' );
			$away = Football_Pool_Utils::post_integer( '_away_' . $match, 'NULL' );
			
			if ( $matches->match_is_editable( $row['match_timestamp'] ) ) {
				if ( is_integer( $home ) && is_integer( $away ) ) {
					$sql = $wpdb->prepare( "REPLACE INTO {$prefix}predictions
											SET userId = %d, 
												matchNr = %d, 
												homeScore = %d, 
												awayScore = %d, 
												hasJoker = %d"
											, $user, $match, $home, $away, ( $joker == $match ? 1 : 0 )
									);
				} else {
					// fix for the multiple-joker-bug
					$sql = $wpdb->prepare( "UPDATE {$prefix}predictions
											SET hasJoker = %d
											WHERE userId = %d AND matchNr = %d"
											, ( $joker == $match ? 1 : 0 ), $user, $match
									);
				}
				
				$wpdb->query( $sql );
			}
		}
		
		// update bonusquestions
		$questions = $this->get_bonus_questions();
		if ( $this->has_bonus_questions ) {
			$answers = array();
			foreach ( $questions as $question ) {
				switch ( $question['type'] ) {
					case 3: // multiple n
						$user_answers = Football_Pool_Utils::post_string_array( '_bonus_' . $question['id'] );
						if ( $question['max_answers'] > 0 && count( $user_answers ) > $question['max_answers'] ) {
							// remove answers from the end of the array
							// (user is cheating or admin changed the max possible answers)
							while ( count( $user_answers ) > $question['max_answers'] ) 
								array_pop( $user_answers );
						}
						$answers[ $question['id'] ] = implode( ';', $user_answers );
						break;
					case 1: // text
					case 2: // multiple 1
					default:
						$bonus_input = '_bonus_' . $question['id'];
						$answers[ $question['id'] ] = Football_Pool_Utils::post_string( $bonus_input );
				}
				
				// add user input to answer (for multiple choice questions) if there is some input
				$user_input = Football_Pool_Utils::post_string( '_bonus_' . $question['id'] . '_userinput' );
				if ( $user_input != '' )
					$answers[ $question['id'] ] .= " {$user_input}";
			}
			$this->update_bonus_user_answers( $questions, $answers, $user );
		}
		
		return true;
	}
	
	private function get_joker() {
		return Football_Pool_Utils::post_integer( '_joker' );
	}
	
	// outputs a prediction form for bonus questions.
	//    wrap:        (optional) if true, wrap the questions in its own form
	//    start_at_nr: (optional) the bonus question numbering will start at that number
	public function prediction_form_questions( $questions, $wrap = false, $id = 1, $start_at_nr = 1 ) {
		$output = '';
		if ( $this->has_bonus_questions ) {
			
			if ( $wrap ) $this->prediction_form_start( $id );
			
			$nr = $start_at_nr;
			foreach ( $questions as $question ) {
				$output .= $this->print_bonus_question( $question, $nr++ );
			}
			
			if ( count( $questions ) > 0 ) {
				$output .= $this->save_button();
			}
			
			if ( $wrap ) $this->prediction_form_end( $id );
		}
		
		return $output;
	}
	
	// outputs a prediction form for matches.
	//    wrap:        (optional) if true, wrap the matches in its own form
	//    id:          unique form id
	public function prediction_form_matches( $matches, $wrap = false, $id = 1 ) {
		$output = '';
		if ( $this->has_matches ) {
			
			if ( $wrap ) $this->prediction_form_start( $id );
			
			global $current_user;
			get_currentuserinfo();
			
			$m = new Football_Pool_Matches;
			$output .= $m->print_matches_for_input( $matches, $id );
			$joker = $m->joker_value;
			$output .= sprintf( '<input type="hidden" id="_joker_%d" name="_joker" value="%d" />', $id, $joker );
			
			if ( count( $matches ) > 0 ) {
				$output .= $this->save_button();
			}
			
			if ( $wrap ) $this->prediction_form_end( $id );
		}
		
		return $output;
	}
	
	public function prediction_form_start( $id = 1) {
		$output = sprintf( '<form id="predictionform-%d" action="%s" method="post">'
							, $id, get_page_link() 
					);
		$output .= wp_nonce_field( FOOTBALLPOOL_NONCE_BLOG, FOOTBALLPOOL_NONCE_FIELD_BLOG, true, false );
		return $output;
	}
	
	public function prediction_form_end( $id = 1 ) {
		return sprintf( '<input type="hidden" id="_action_%d" name="_fp_action" value="update" /></form>', $id );
	}
	
	public function print_bonus_question_for_user( $questions ) {
		$output = '';
		$nr = 1;
		$statspage = Football_Pool::get_page_link( 'statistics' );
		
		foreach ( $questions as $question ) {
			if ( $this->always_show_predictions || ! $this->bonus_is_editable( $question['question_date'] ) ) {
				$output .= '<div class="bonus userview">';
				$output .= sprintf( '<p class="question"><span class="nr">%d.</span> %s</p>'
									, $nr++
									, $question['question'] 
							);
				$output .= '<span class="bonus points">';
				if ( $question['scoreDate'] ) {
					// standard points or alternate points as reward for question?
					$points = ( $question['userPoints'] != 0 ) ? $question['userPoints'] : $question['points'];
					$output .= sprintf( '%d %s ', 
										( $question['correct'] * $points ),
										__( 'points', FOOTBALLPOOL_TEXT_DOMAIN )
								);
				}
				$output .= sprintf( '<a title="%s" href="%s">', 
									__( 'view other users answers', FOOTBALLPOOL_TEXT_DOMAIN )
									, esc_url(
										add_query_arg( 
											array( 'view' => 'bonusquestion', 'question' => $question['id'] ), 
											$statspage
										)
									) 
							);
				$output .= sprintf( '<img alt="%s" src="%sassets/images/site/charts.png" />',
									__( 'view other users answers', FOOTBALLPOOL_TEXT_DOMAIN ), FOOTBALLPOOL_PLUGIN_URL );
				$output .= '</a></span>';
				$output .= sprintf( '<p>%s: %s</p>',
									__( 'answer', FOOTBALLPOOL_TEXT_DOMAIN ),
									( $question['answer'] != '' ? $question['answer'] : '...' )
							);
				$output .= '</div>';
			}
		}
		
		return $output;
	}
	
	public function bonus_is_editable( $ts ) {
		if ( $this->force_lock_time ) {
			$editable = ( current_time( 'timestamp' ) < $this->lock_timestamp );
		} else {
			$diff = $ts - time();
			$editable = ( $diff > $this->lock_timestamp );
		}
		
		return $editable;
	}

	public function get_bonus_question_answers_for_users( $question = 0 ) {
		if ( $question == 0 ) return array();
		
		global $wpdb;
		$prefix = FOOTBALLPOOL_DB_PREFIX;
		
		$sql = "SELECT u.ID AS userId, u.display_name AS name, a.answer, a.correct, a.points
				FROM {$prefix}bonusquestions_useranswers a 
				RIGHT OUTER JOIN {$wpdb->users} u
					ON ( a.questionId = %d AND a.userId = u.ID ) ";
		if ( $this->has_leagues ) {
			$sql .= "INNER JOIN {$prefix}league_users lu ON ( u.ID = lu.userId ) ";
			$sql .= "INNER JOIN {$prefix}leagues l ON ( lu.leagueId = l.ID ) ";
		} else {
			$sql .= "LEFT OUTER JOIN {$prefix}league_users lu ON ( lu.userId = u.ID ) ";
			$sql .= "WHERE ( lu.leagueId <> 0 OR lu.leagueId IS NULL ) ";
		}
		$sql .= "ORDER BY u.display_name ASC";
		$sql = $wpdb->prepare( $sql, $question );
		$rows = $wpdb->get_results( $sql, ARRAY_A );
		return $rows;
	}
	
	private function save_button() {
		return sprintf( '<div class="buttonblock"><input type="submit" name="_submit" value="%s" /></div>',
						__( 'Save', FOOTBALLPOOL_TEXT_DOMAIN )
				);
	}
	
	public function get_avatar( $user_id, $size = 'small', $wrap = true ) {
		if ( ! $this->show_avatar ) return;
		
		if ( ! is_int( $size ) ) {
			switch ( $size ) {
				case 'large':
					$size = FOOTBALLPOOL_LARGE_AVATAR;
					break;
				case 'medium':
					$size = FOOTBALLPOOL_MEDIUM_AVATAR;
					break;
				case 'small':
				default:
					$size = FOOTBALLPOOL_SMALL_AVATAR;
			}
		}
		
		$avatar = get_avatar( $user_id, $size );
		if ( $wrap )
			return sprintf( '<span class="fp-avatar">%s</span>', $avatar );
		else
			return $avatar;
	}
}
?>
Return current item: Football Pool