HEX
Server: Apache
System: Linux pdx1-shared-a1-38 6.6.104-grsec-jammy+ #3 SMP Tue Sep 16 00:28:11 UTC 2025 x86_64
User: mmickelson (3396398)
PHP: 8.1.31
Disabled: NONE
Upload Files
File: /home/mmickelson/church.martyknows.com/wp-content/plugins/taskfreak/inc/models/item.php
<?php

/*
@package TaskFreak
@since 0.1
@version 1.0

** TASK STATUS ********

	0 => Draft
	20 => In Progress
	30 => Suspended
	60 => Closed

*/

class tfk_item extends tzn_model
{

	public function __construct() {
		parent::__construct(
			'item',
			array(
				'item_id'	 		=> 'UID',
				'project'			=> 'OBJ',
				'priority'			=> 'NUM',
				'title'				=> 'STR',
				'description'		=> 'HTM',
	            'deadline_date'     => 'DTE',
				'creation_date'		=> 'DTM',
				'user_id'			=> 'NUM',
	            'author_id'       	=> 'NUM'
			)
		);
		
		$this->errors['global_errors'] = '';
		$this->errors['status'] = '';
		$this->errors['file'] = '';
		foreach ($this->properties as $k => $v) {
			$this->errors[$k] = '';
		}
	}

	/**
	 * check before saving task
	 */
	public function check() {
		$msg = array();
		if (empty($this->data['title'])) {
			$msg['title'] = __('Title should not be blank', 'taskfreak');
		}
		if (!preg_match('/^[123]$/', $this->data['priority'])) {
			$msg['priority'] = __('Priority should be 1, 2, or 3', 'taskfreak');
		}
		if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $this->data['deadline_date'])) {
			$msg['deadline_date'] = __('Invalid date format', 'taskfreak');
		}
		if (strlen($this->data['description']) > 65535) {
			$msg['description'] = __('Description is too big (maybe you pasted an image into it?)', 'taskfreak');
		}
		
		foreach ($msg as $k => $v) {
			$this->errors[$k] = '<p class="tfk_err">'.__($v, 'taskfreak').'</p>';
		}
		return empty($msg);
	}
	
	/**
	 * load current task status
	 * @return tfk_item_status
	 */
	public function get_status() {
		$obj = new tfk_item_status();
		if ($pid = $this->get_uid()) {
			if ($obj->load_list(array(
					'where'	=> 'item_id='.$pid.' AND action_code <> "" AND comment_id = 0',
					'order'	=> 'log_date DESC',
					'limit' => 1
			))) {
				return $obj->next(true);
			}
		}
	
		// no status (new item or not loaded)
		$obj->set('action_code', 0);
		$obj->set('log_date', 'NOW');
		return $obj;
	}
	
	/**
	 * set task status
	 * @param number $status status code
	 * @param number $uid (optional) id of the user setting the status
	 * @param string $info (optional) may contain "creation" for example 
	 * @return boolean
	 */
	public function set_status($status, $uid=null, $info=null) {
		if (is_null($uid)) {
			$uid = get_current_user_id();
		}
		$obj = new tfk_item_status();
		$obj->set('item_id', $this->get_uid());
		$obj->set('log_date','NOW');
		$obj->set('action_code', $status);
		$obj->set_object('user', $uid);
		if (!is_null($info)) {
			$obj->set('info', $info);
		}
		return $obj->insert('ignore');
	}
	
	/**
	 * get deadline proximity bar width in pixels
	 * @return number
	 */
	public function get_deadline_proximity_bar() {
		if ($this->data['deadline_date'] == '0000-00-00 00:00:00') {
			return 300;
		}
		$date_diff_in_sec = strtotime($this->data['deadline_date']) + 24*60*60 - time() - tzn_tools::get_user_timezone_offset(); 
		if ($date_diff_in_sec < 86400)
			return 300; // px
		elseif ($date_diff_in_sec > 365 * 86400)
			return 30;
		else
			return 300 - ($date_diff_in_sec / 86400 * 270 / 365);
	}
	
	/**
	 * get deadline proximity in words
	 * @return string
	 */
	public function get_deadline_proximity() {
		if ($this->data['deadline_date'] == '0000-00-00 00:00:00')
			return __('Undefined Deadline', 'taskfreak');

		$date_diff['sec'] = strtotime($this->data['deadline_date']) + 24*60*60 - time() - tzn_tools::get_user_timezone_offset();		
		$date_diff['min'] = round($date_diff['sec'] / 60);
		$date_diff['hour'] = round($date_diff['sec'] / 3600);
		$date_diff['day'] = round($date_diff['sec'] / 86400);
		$date_diff['month'] = round($date_diff['sec'] / 2592000);
		if ($date_diff['sec'] < 0) {
			return __("Past due !", 'taskfreak');
		}
		if ($date_diff['sec'] < 3600) {
			return sprintf(_n("%s minute remaining", "%s minutes remaining", $date_diff['min'], 'taskfreak'), $date_diff['min']);
		}
		if ($date_diff['sec'] < 86400) {
			return sprintf(_n("%s hour remaining", "%s hours remaining", $date_diff['hour'], 'taskfreak'), $date_diff['hour']);
		}
		if ($date_diff['sec'] < 365 * 86400) {
			return sprintf(_n("%s day remaining", "%s days remaining", $date_diff['day'], 'taskfreak'), $date_diff['day']);
		}
		return sprintf(_n("%s month remaining", "%s months remaining", $date_diff['month'], 'taskfreak'), $date_diff['month']);
	}
	
	/**
	 * get an extract from the description (body) of the task
	 * useful for the HTML title attribute
	 * @return string
	 */
	public function get_description_extract() {
		$description_text = preg_replace('/&nbsp;/', ' ', $this->data['description']);
		$description_text = html_entity_decode(strip_tags($description_text));
		if (strlen($description_text) > 140) {
			$description_text = substr($description_text, 0, 140);
			$description_text = preg_replace('/\w+$/', '', $description_text); // prevent cutting words
			$description_text .= '[…]';
		}
		return htmlspecialchars($description_text);
	}
}

class tfk_item_status extends tfk_log {

	public function __construct() {
		parent::__construct();
	}
	
	/**
	 * get a list of statuses in translated strings
	 * @param mixed $draft include draft status ?
	 * @param string $context by default, context for translation is "many tasks"
	 * @return array
	 */
	public static function get_status_list($draft, $context='many tasks') {
		$arr = array(
    		0 		=> _x('Draft', $context, 'taskfreak'),
    		20 		=> _x('In Progress', $context, 'taskfreak'),
    		30 		=> _x('Suspended', $context, 'taskfreak'),
    		60 		=> _x('Closed', $context, 'taskfreak')
		);
    	if (!$draft) {
	    	unset($arr[0]);
    	}
    	return $arr;
	}
	
	/**
	 * get task current status in string
	 * @return string
	 */
	public function get_status() {
		$key = $this->get('action_code',0);
		switch($key) {
			case 0:
				return __('Draft', 'taskfreak');
			case 20:
				return __('In Progress', 'taskfreak');
			case 30:
				return __('Suspended', 'taskfreak');
			case 60:
				return __('Closed', 'taskfreak');
			default:
				return __('Unknown Status !', 'taskfreak');
		}
	}
	
	/**
	 * get an HTML select (dropdown list) of task statuses
	 * @param string $name name of the HTML select element
	 * @param number $selected (optional) current status, 0 by default
	 * @param string $xtra (optional) class for the select element
	 * @param string $draft (optional) include draft status ?
	 * @return string
	 */
	public static function list_select($name, $selected=0, $xtra='class="tzn_option_select"', $draft=true) {
		return tzn_tools::form_select($name, self::get_status_list($draft), $selected, $xtra);
    }
    
    /**
     * get a HTML li (list) of links to task statuses + "All tasks" for filtering status
     * @param unknown $url base URL
     * @param unknown $name name of the filter argument inside the query
     * @param unknown $current current filter
     * @return string
     */
    public static function list_links($url, $name, $current) {
	    return  tzn_tools::form_links($url, $name, array('all' => _x('All tasks', 'tasks', 'taskfreak')), $current) 
	    		.tzn_tools::form_links($url, $name, self::get_status_list(is_user_logged_in()), $current);
    }
	
}

class tfk_item_comment extends tzn_model
{
	public function __construct() {
		parent::__construct(
				'item_comment',
				array(
						'item_comment_id'	=> 'UID',
						'item_id'	 		=> 'NUM',
						'user_id'			=> 'NUM',
						'post_date'			=> 'DTM',
						'body'				=> 'HTM',
			            'last_change_date'	=> 'DTM'
				)
		);
		
		$this->errors['global_errors'] = '';
		$this->errors['file'] = '';
		foreach ($this->properties as $k => $v) {
			$this->errors[$k] = '';
		}
	}
	
	/**
	 * check task before saving
	 * @return boolean
	 */
	public function check() {
		if (empty($this->data['body'])) {
			$this->errors["body"] = '<p class="tfk_err">'.__('Empty comment not allowed.', 'taskfreak').'</p>';
			return false;
		}
		return true;
	}
}

class tfk_item_file extends tzn_model
{
	public function __construct() {
		$this->error = '';
		parent::__construct(
				'item_file',
				array(
						'item_file_id'		=> 'UID',
						'item_id'	 		=> 'NUM',
						'user_id'			=> 'NUM',
						'file_title'		=> 'STR',
						'file_name'			=> 'STR',
						'file_type'			=> 'STR',
						'file_size'			=> 'NUM',
						'post_date'			=> 'DTM',
						'last_change_date'	=> 'DTM',
						'file_tags'			=> 'STR'
				)
		);
	}
	
	/**
	 * save uploaded file to disk and metadata into database
	 * @param array $file_input file input information from PHP
	 * @return boolean
	 */
	public function upload($file_input) {
		$wp_upload_dir = wp_upload_dir();
		if ($_FILES[$file_input]['error'] == UPLOAD_ERR_OK) {
			if (preg_match('/(php|phtml|phps)/i', $_FILES[$file_input]['type'])
			|| preg_match('/\.(php|phtml|phps)$/i', $_FILES[$file_input]['name'])) {
				$this->error = __('Forbidden upload: ', 'taskfreak').$_FILES[$file_input]['name'];
				return false;
			} else {
				$file_title = $_FILES[$file_input]['name'];
				$file_name = time().'_'.preg_replace('/[^\w\d.-_,]/', '-', $file_title);
				if (!@move_uploaded_file($_FILES[$file_input]['tmp_name'], $wp_upload_dir['path'].'/'.$file_name)) {
					$this->error = __('Unable to write file to upload directory. Please tell an admin.', 'taskfreak');
					return false;
				} else {
					@chmod($wp_upload_dir['path'].'/'.$file_name, 0444);
					$this->set('file_title', $file_title);
					$this->set('file_name', $file_name);
					$this->set('file_size', $_FILES[$file_input]['size']);
					$this->set('file_type', $_FILES[$file_input]['type']);
					$this->set('post_date', 'NOW');
					return true;
				}
			}
		} else {
			$this->error = __('Upload of ', 'taskfreak').$_FILES[$file_input]['name'].__(' failed with error code ', 'taskfreak').$_FILES[$file_input]['error'];
			return false;
		}
	}
}

class tfk_item_info extends tfk_item
{
	public function __construct() {
		parent::__construct();
		$this->properties['comment_count'] = 'NUM';
		$this->properties['file_count'] = 'NUM'; 
		$this->properties['item_status'] = 'OBJ';
		$this->properties['item_status_info'] = 'STR';
		$this->properties['item_status_date'] = 'DTM';
		$this->properties['item_status_user_id'] = 'NUM';
		$this->properties['proximity'] = 'NUM';
	}
	
	/**
	 * (non-PHPdoc)
	 * @see tzn_model::load_list()
	 */
	public function load_list($args=null) { 
		$sql = array(
				'sql' 	=> 'SELECT '.$this->db_alias().'.*,
							IFNULL(DATEDIFF('.$this->db_alias().'.deadline_date, NOW()), 9999999999) AS proximity,
							project.project_id,
							project.name,
							project.description AS project_description,
							project.who_read,
							project.who_comment,
							project.who_post,
							project.who_manage,
							project.trashed,
							user.*,
							item_status.log_date AS item_status_date,
							item_status.info AS item_status_info,
							COUNT(DISTINCT item_comment.item_comment_id) AS comment_count,
							COUNT(DISTINCT item_file.item_file_id) AS file_count,
							(SELECT action_code
								FROM '.$this->db_table('log').'
								WHERE item_id = item.item_id
								AND comment_id = 0
								AND action_code <> ""
								ORDER BY log_date DESC
								LIMIT 1) AS item_status_action_code,
							item_status.user_id AS item_status_user_id
							FROM '.$this->db_table().' AS '.$this->db_alias().'
							INNER JOIN '.$this->db_table('project').' AS project ON item.project_id = project.project_id
							INNER JOIN '.$this->db_table('log').' AS item_status ON item.item_id = item_status.item_id
							LEFT JOIN '.$this->db->base_prefix.'users AS user ON user.ID = item.user_id
							LEFT JOIN '.$this->db_table('item_comment').' AS item_comment ON item.item_id = item_comment.item_id
							LEFT JOIN '.$this->db_table('item_file').' AS item_file ON item.item_id = item_file.item_id AND file_tags = "task"
							',
				'count'	=> true,
				'group' => $this->db_alias().'.item_id'
		);
	
		$where = 'item_status.log_date = (
											SELECT MAX(log_date) 
											FROM '.$this->db_table('log').' 
											WHERE item_id = item.item_id
											AND comment_id = 0 
										)';
	
		if ($args) {
			if (isset($args['where'])) {
				$where .= ' AND '.$args['where'];
				unset($args['where']);
			}
			$args['where'] = $where;
			$args = array_merge($args, $sql);
		} else {
			$sql['where'] = $where;
			$args = $sql;
		}
		return parent::load_list($args);
	}
	
	/**
	 * get number of attachments in string (for an HTML title attribute)
	 * @return string
	 */
	public function get_attachments() {
		return sprintf(_n('%d attachment', '%d attachments', $this->data['file_count'], 'taskfreak'), $this->data['file_count']);
	}
}

class tfk_item_status_info extends tfk_item_status {

	public function __construct() {
		parent::__construct();
		$this->properties['type'] = 'STR';
		$this->properties['creation_date'] = 'DTM';
		$this->properties['title_or_name'] = 'STR';
	}

	/**
	 * (non-PHPdoc)
	 * @see tzn_model::load_list()
	 */
	public function load_list($args=null) {
		$sql = array(
				'sql'=> 'SELECT log.*,
					user.display_name,
					IF (log.item_id = 0, "project", "task") AS type, 
					IFNULL(title, project.name) AS title_or_name,
					IFNULL(item.creation_date, project.creation_date) AS creation_date,
					IFNULL(who_read,
						(SELECT who_read FROM '.$this->db_table('project').' WHERE project_id = item.project_id LIMIT 1)
					) AS who_read,
					IF( log.project_id = 0,
						(SELECT action_code FROM '.$this->db_table().'
						 WHERE item_id = item.item_id
						 AND action_code <> ""
						 AND comment_id = 0
						 ORDER BY log_date DESC LIMIT 1),
						(SELECT action_code FROM '.$this->db_table().'
						 WHERE project_id = project.project_id
						 AND action_code <> ""
						 AND comment_id = 0
						 ORDER BY log_date DESC limit 1)
					) AS status
					FROM '.$this->db_table().' AS log
					LEFT JOIN '.$this->db_table('item').' AS item ON item.item_id = log.item_id
					LEFT JOIN '.$this->db_table('project').' AS project ON project.project_id = log.project_id
					LEFT JOIN '.$this->db->base_prefix.'users AS user ON user.ID = log.user_id',
				'where' => '(log.item_id <> 0 OR project.trashed = 0)',
				'having'=> tfk_user::get_roles_sql('who_read').' AND status > 0',
				'order' => 'log_date DESC'
		);

		if (isset($args['where']))
			$sql['where'] .= ' AND '.$args['where'];

		return parent::load_list($sql);
	}
}