Location: PHPKode > scripts > Lightweight Club Calendar > lc-calendar-0.9.4/core/lcc_view.class.php
<?php
/**
* Classfile for LCC-view
*
* PHP Version 4
*
* @author <hide@address.com>
* @copyright Copyright (c) 2006, Benedikt Hallinger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, 51 Franklin St, Fifth Floor,
* Boston, MA  02110-1301  USA
*/
include_once 'smarty/libs/Smarty.class.php';
@include_once 'Calendar/Decorator.php';
@include_once 'Calendar/Month/Weekdays.php';
@include_once 'Calendar/Decorator/Uri.php';
class_exists('Calendar') or die('<b>LCC SETUP ERROR:</b> PEAR::Calendar not found! Please make sure that it is properly installed.');

/**
* The view of LCC
*
* It implements SMARTY and provides some sort of output buffering
* so the templates can be as flexible as they are needed.
* Additionally Language stuff is handled here
*/
class LCC_View extends Smarty {
	/**
	* Content of language file
	*
	* @var array
	* @access private
	*/
	var $_lang = false;
	
	/**
	* Storage for the messages
	*
	* @var array
	* @access private
	*/
	var $_messages = array();
	
	/**
	* Storage for content
	*
	* Here all HTML will be stored that must be printed
	* after the messages
	*
	* @var array
	* @access private
	*/
	var $_content = array();
	
	/**
	* Storage for buffered content while in direct rendering mode
	*
	* @var array
	* @access private
	*/
	var $_content_direct = array();
	
	/**
	* Silence state; if we are in silent mode, we don't add content
	*
	* @var boolean
	* @see setSilent();
	*/
	var $silent = false;
	
	/**
	* If direct rendering is enabled
	*
	* @var boolean
	* @see setDirectRendering();
	*/
	var $direct_rendering = false;
	
	/**
	* Assigned tpl vars when calling {@link LCC_View::setDirectRendering()}
	*
	* Those variables need to be reassigned after direct rendering mode
	*
	* @var array
	*/
	var $tpl_vars_predirect = array();
	
	/**
	* A Reference to a LCC_Core object
	*
	* This reference gets used to get or process data
	*
	* @var LCC_Core
	* @access private
	*/
	var $_lcc_core;

	/**
	* Constructor
	*
	* Constructs the view object to give the core the ability to display its data.
	* This in fact sets up smarty to our needs.
	*
	* @param LCC_Core  $lcc_core    LCC_Core object
	*/
	function LCC_View(&$lcc_core)
	{
		// Store LCC-Core object
		if (is_a($lcc_core, 'LCC_Core')) {
			$this->_lcc_core =& $lcc_core;
		} else {
			die('LCC_View ERROR: Unable to initialize view instance! $lcc_core is no object of class LCC_Core!');
		}
		
		$compile_dir = $this->_lcc_core->getConfig('data_dir').'/smarty_c/';
		
		// Initialize smarty
		$this->Smarty();
		if (is_writable($compile_dir)) {
			$this->compile_dir = $compile_dir;
		} else {
			die('<b>LCC_View ERROR:</b> The SMARTY compile directory ('.$compile_dir.') does not exist or is not writable!');
		}
		
		// Set default design
		$this->selectDesign($this->_lcc_core->getConfig('design'));
		
		// Set caching
		$this->setCaching($this->_lcc_core->getConfig('caching'));
		
		// Assign the LCC_Core and LCC_View object to all templates by default
		$this->assign_by_ref('LCC_CORE',        $this->_lcc_core);
		$this->assign_by_ref('LCC_VIEW',        $this);
		$this->assign_by_ref('LCC_AUTHED_USER', $this->_lcc_core->getAuthedUser(true));
		
		// Register some modifiers
		$this->register_modifier('toMonthString', array(&$this, 'toMonthString'));
		$this->register_modifier('convertDate', array(&$this, 'convertDate'));
		$this->register_modifier('convertTime', array(&$this, 'convertTime'));
	}

	/**
	* Select the design wich should be used
	*
	* @param string $design     Name (path) of the design under templates/
	*/
	function selectDesign($design)
	{
		if (!$design) {
			die('<b>LCC_View->selectDesign()</b> No Design selected!');
		}
		
		// checks on design path
		$design_path = $this->_lcc_core->getConfig('design_dir').'/'.$design;
		if (strlen($design) != 0 && is_readable($design_path)) {
			$this->template_dir = $design_path;
		} else {
			die('<b>LCC_View->selectDesign()</b> Design ('.$design.') does not exist or is not readable!');
		}
		
		// checks on neccessary design files
		$tpl_list = array(
		                  'design.css', 'calendar.tpl',
		                  'event_view.tpl', 'message.tpl',
		                  'note_editable.tpl', 'note_view.tpl',
		                 );
		foreach ($tpl_list as $tpl) {
			if (!$this->template_exists($tpl)) {
				die('<b>LCC_View->selectdesign()</b> Design ('.$design.') does not have the mandatory template "'.$tpl.'"!');
			}
		}
		
		$this->loadLanguageFile();
	}
	
	/**
	* Make view silent - dont render if render() is called
	*
	* @param boolean $param
	*/
	function setSilent($param)
	{
		if ($param) {
			$this->silent = true;
		} else {
			$this->silent = false;
		}
	}
	
	/**
	* Enable direct output, so it can be fetched in templates
	*
	* @param boolean $param
	*/
	function setDirectRendering($param)
	{
		if ($param) {
			$this->direct_rendering = true;
			$this->tpl_vars_predirect = $this->get_template_vars();
		} else {
			$this->direct_rendering = false;
		}
	}
	
	/**
	* Print out the style definition for the selected Design
	*
	* This parses the "design.css" file of the selected Design using SMARTY,
	* So we have a maximum flexibility (even executing PHP!) inside the css.
	* This could be used (for example) to dynamically fetch the
	* style-sheet of an external application.
	*
	* @todo invent something, so CSS will be cached by the browser.
	*       The problem is, that users do not always have full control
	*       over the <head> Part of their site, for example if
	*       LCC runs integrated in some CMS
	*/
	function printCSS()
	{
		echo '<style type="text/css"> <!--';
		echo $this->fetch('design.css');  // Parse CSS with Smarty
		echo '--></style>';
	}
	
	/**
	* Enables/disables SMARTYs caching
	*
	* @todo DISABLED! SMARTY Caching is a tweaky process - we need some cache-id manager
	* @param boolean $should_i
	*/
	function setCaching($should_i)
	{
		/*
		if ($should_i) {
			$this->caching = true;
		} else {
			$this->caching = false;
		}
		*/
		$this->caching = false;
	}
	
	/**
	* Gets the language of the user or a fallback value
	*
	* The language can only be set if the user sends a header AND
	* the design has a language file for that language
	*
	* @return string     example: 'en'
	* @todo we should implement cookie support so the user can change his language
	*/
	function getUserLanguage()
	{
		$fallback = 'en';
		
		/*
		* Try to fetch language from HTTP-Header
		*/
		if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) {
			$lang_array = strtolower($_SERVER["HTTP_ACCEPT_LANGUAGE"]);
			$languages = explode(",", $lang_array);
			
			$all_langs = array();
			
			// Try to fetch the main language (2 chars) with the highest priority
			foreach ($languages as $language_list) {
				$this_row = split(';', $language_list);
				if (isset($this_row[1])) {
					$priority_raw = split('=',$this_row[1]);
					$priority = $priority_raw[1] * 100; // convert to a percent value
					$all_langs[$priority] = substr($language_list, 0, 2);
				} else {
					// this is just a sublanguage (en-en), store for later use
					$all_sublangs[] = $this_row;
				}
			}
			
			// now fetch the language with the highest priority
			if (is_array($all_langs) && count($all_langs) > 0) {
				krsort($all_langs); // highest priority will be first element
				$selected_lang = array_shift($all_langs);
			} else {
				// we had no main language, try to derive from sublanguages
				if (is_array($all_sublangs) && count($all_sublangs) > 0) {
					$firstsublang = array_shift($all_sublangs);
					$selected_lang = substr($firstsublang, 0, 2);
				} else {
					$selected_lang = $fallback; // in case we had no sublanguages
				}
			}
		} else {
			// Use fallback in case no language was transmitted in HTTP-Header
			$selected_lang = $fallback;
		}
		
		// Make sure that the code is valid
		if (!preg_match('/^\w\w$/i', $selected_lang)) {
			$selected_lang = $fallback;
		}
		
		/*
		* check if language file for the found user language exists
		* if not -> Fallback value $fallback
		*/
		$core_langfile          = dirname(__FILE__).'/lang/'.$selected_lang.'.conf';
		$core_langfile_fallback = dirname(__FILE__).'/lang/'.$fallback.'.conf';
		if (!is_readable($core_langfile)) {
			if (!is_readable($core_langfile_fallback)) {
				die('<b>LCC_View LANGUAGE ERROR:</b> Language file '.$core_langfile_fallback.' not found and fallback language file '.$fallback.' not found!');
			} else {
				$selected_lang = $fallback;
			}
		}
		return $selected_lang;
	}
	
	/**
	* Loads the language files
	*
	* Firstly the core language file for the user seelcted laguage is loaded.
	* If no core language file for that language could be found, then a fallback
	* file (en.conf) will be loaded.
	* After that, the design specific language file is loaded, if present for that language.
	*
	* @return array     Language variables of that file
	*/
	function loadLanguageFile()
	{
		if (!$this->_lang) {
			$this->clear_config();
			$selected_lang = $this->getUserLanguage();  // contains either the real or a fallback language
			
			$core_langfile   = dirname(__FILE__).'/lang/'.$selected_lang.'.conf';
			$custom_langfile = $this->template_dir.'/lang/'.$selected_lang.'.conf';
			
			// Load core language file
			// That this language file is present is ensured from getUserLanguage()
			$this->config_load($core_langfile);
			
			// Load design specific language file, if there is one
			if (is_readable($custom_langfile)) {
				$this->config_load($custom_langfile);
			}
			
			// Save merged language files
			$this->_lang = $this->get_config_vars();
		}
		return $this->_lang;
	}
	
	/**
	* Gets the translation for a language variable
	*
	* @param string $lang_var   Language variable to translate
	* @return string
	*/
	function getLang($lang_var)
	{
		$all_lang = $this->loadLanguageFile();
		
		if (array_key_exists($lang_var, $all_lang)) {
			return htmlentities($all_lang[$lang_var], ENT_QUOTES);
		} else {
			return htmlentities("-- '$lang_var' NEEDS TO BE TRANSLATED --", ENT_QUOTES);
		}
	}
	
	/**
	* This returns the correct name for the day based on language file and start of week
	*
	* The translations of the day names are in the language file.
	* Since the first day of week can change, this method should be used in templates so
	* the correct day is always translated.
	* $day = 0 is considered as Sunday, so the language variables should look like:
	* weekday_0 = "Sun"
	* weekday_1 = "Mon" and so on
	*
	* @param int $day    Number of the day to translate
	* @return string
	*/
	function getWeekDayName($day)
	{
		$weekstart = $this->_lcc_core->getConfig('start_of_week');
		$correct_day = $day + $weekstart;
		
		if ($correct_day > 6) {
			$correct_day = $correct_day - 7;
		}
		
		return $this->getLang('weekdayname_'.$correct_day);
	}
	
	/**
	* Adds a message to be displayed
	*
	* This method is mainly used by the LCC_Message object to display
	* itself; but of course you may use it to assign messages yourself.
	*
	* @param LCC_Message $msg_o    LCC_Message object
	*/
	function addMessage($msg_o)
	{
		if (is_a($msg_o, 'LCC_Message')) {
			$this->assign('message', $msg_o);
			array_push($this->_messages, $this->fetch('message.tpl'));
		} else {
			die('<b>LCC_View->addMessage() ERROR:</b> Parameter is no LCC_Message object!');
		}
	}
	
	/**
	* This flushes the cache for direct rendering and returns its content to the caller
	*
	* @return string
	*/
	function flushDirectRenderingCache()
	{
		$html = '';
		foreach ($this->_content_direct as $content) {
			$html .= $content;
		}
		$this->_content_direct = array();
		
		// reassign all variables that were assigned before direct rendering mode
		foreach ($this->tpl_vars_predirect as $tplvar => $value) {
			$this->assign($tplvar, $value);
		}
		$this->tpl_vars_predirect = array();
		return $content;
	}
	
	/**
	* Wrapper for smartys display()
	*
	* This is an emulation for display(). It fetches the html
	* but instead of direct outputting it, it wil be buffered
	* for render()
	* In direct rendering mode, the content of $tpl will just be returned
	* There is a "silent" mode that prevents adding of content.
	*
	* @param string $tpl    Name of Template
	*/
	function display($tpl)
	{
		if (!$this->silent) {
			if ($this->direct_rendering) {
				// a custom action should be performed, so cache until next
				// display() call without custom action
				array_push($this->_content_direct, $this->fetch($tpl));
			} else {
				array_push($this->_content, $this->fetch($tpl));
				
				// flush remaining buffer, if it was not cleared before
				array_push($this->_content, $this->flushDirectRenderingCache());
			}
		}
	}
	
	/**
	* Like assign() but encodes newlines of all elements of data
	*
	* @param string $tpl_var  Name of the Template variable to assign
	* @param string|array $data
	*/
	function assign_encoded($tpl_var, $data) {
		if (is_array($data)) {
			foreach ($data as $key => $value) {
				if (is_array($value)) {
					foreach ($value as $key2 => $value2) {
						$data[$key][$key2] = nl2br($value2);
					}
				} else {
					$data[$key] = nl2br($value);
				}
			}
		} else {
			$data = nl2br($data);
		}
		$this->assign($tpl_var, $data);
	}
	
	/**
	* Print content of LCC_View
	*
	* This will first print all available messages
	* and then the content will be printed.
	*
	* There is a "silent" mode that prevents rendering.
	*
	* @see setSilent()
	*/
	function render()
	{
		if (!$this->silent) {
			// Print out any messages present,
			// if not in direct rendering mode
			if (!$this->direct_rendering) {
				foreach ($this->_messages as $msg_html) {
					echo $msg_html;
				}
			}
			
			// print content
			foreach ($this->_content as $c_html) {
				echo $c_html;
			}
		}
	}
	
	/**
	* Prints edit view of an event
	*
	* This encodes newlines in data fields and also resolves
	* the subscribed users raw metadata to an array
	*
	* @param array $data    Data array of that event
	*/
	function printEventDetails($event_data)
	{
		// Get instance of the selected EventDriver
		$eventDriver =& $this->_lcc_core->getEventDriverInstance();
		
		// Get subscriptions
		$subscriptions = $eventDriver->getSubscribedUsers($event_data['id']);
		foreach ($subscriptions as $cat => $members) {
			foreach ($members as $member_key => $member) {
				$subscriptions[$cat][$member_key] = htmlentities($member, ENT_QUOTES);
			}
		}
		$this->assign('subscribed_yes',     $subscriptions['yes']);
		$this->assign('subscribed_no',      $subscriptions['no']);
		$this->assign('subscribed_unknown', $subscriptions['unknown']);
		
		$this->assign_encoded('data', $event_data);
		$this->display('event_view.tpl');
	}
	
	/**
	* Prints a form to add notes to an event
	*
	* @param array $note_data     Data of the note
	*/
	function printNoteForm($note_data)
	{
		$this->assign('data', $note_data);
		$this->display('note_editable.tpl');
	}
	
	/**
	* Prints notes
	*
	* @param array $note_data     Data of the notes of the event
	*/
	function printNotes($note_data)
	{
		$this->assign_encoded('data', $note_data);
		$this->display('note_view.tpl');
	}
	
	/**
	* Generates the calendar for a specific month
	*
	* The events data is processed and fetched from LCC_Core!
	* LCC_View only handles outputting stuff.
	*
	* The last viewed calendar state is remembered in session.
	* If GET values for selection of year/month is provided, this
	* will override the session. If nothing is passed neither from
	* GET nor from session, then NOW is assumed.
	*/
	function printMonthCalendar()
	{
		$this->_lcc_core->verifyPermission('calendar_view');
		$weekstart = $this->_lcc_core->getConfig('start_of_week');
		
		// Get the selected month and year
		if (is_numeric($_GET['lcc_year']) && preg_match("/^\d\d\d\d$/",$_GET['lcc_year'])
		 && is_numeric($_GET['lcc_month']) && preg_match("/^\d\d?$/", $_GET['lcc_month'])) {
			$year  = $_GET['lcc_year'];
			$month = $_GET['lcc_month'];
		} elseif (preg_match("/^\d\d\d\d$/", $_SESSION['lcc_year']) && preg_match("/^\d\d?$/", $_SESSION['lcc_month'])) {
			$year  = $_SESSION['lcc_year'];
			$month = $_SESSION['lcc_month'];
		} else {
			// assume today if nothing is stored, passed or if formats are not okay
			$year  = date('Y');
			$month = date('n');
		}
		
		// Get instance of the selected EventDriver
		$eventDriver =& $this->_lcc_core->getEventDriverInstance();
		
		// Get the months of the selected date
		$Month = new Calendar_Month_Weekdays($year, $month, $weekstart);
		
		// Correct invalid dates if neccesary
		if(!$Month->isValid()){
			$Month->adjust();
		}
		
		// generate day objects inside the month
		$Month->build();
		
		/*
		* generate Calendar HTML
		*/
		$weeks = array();
		$fetched_events = array(); // This will contain the events data so we need to fetch the event data only once

		// Loop over each day of this month
		// and fill the month with data.
		// $calendar_data will have the following structure:
		// array( [week1] => array(
		//                       [day1] => array(
		//                              'day_of_month' => 12,
		//                              'current_month' => true,
		//                              'events' => array(
		//                                       [event_id1] => array => (
		//                                                     [event field1] => [value],
		//                                                     [event field2] => [value],
		//                                                             ...
		//                                                  )
		//                                           ),
		//                                        [event_id2] => array => (
		//                                                     [event field1] => [value],
		//                                                     [event field2] => [value],
		//                                                             ...
		//                                                  )
		//                               )
		//                         [day2] => array( ... )
		//                                ...
		//                    ),
		//         [week2] => array( ... )
		//  )
		// In words: The first indexed dimension contains a array with the days of the
		// specific week, each day of the weekcontains some metadata along with an array
		// containing events. The events in turn contain an array containing the events data.
		$calendar_data = array();
		$week_counter = 0;
		while ($Day = $Month->fetch()) {

			// This is where we store this days data
			$current_day_data = array();
			
			// Is this day the first of a new week?
			if ($Day->isFirst()) {
				$week_counter++;
			}
			
			// Does this day belong to the current month?
			if (!$Day->isEmpty()) {
				$current_day_data['current_month'] = true;
			} else {
				$current_day_data['current_month'] = false;
			}
			
			// Store this days date
			$current_day_data['day_of_month'] = $Day->thisDay('int');
			
			// Fetch all events of this day
			$current_day_data['events'] = array();
			$days_events = $eventDriver->getEventsForDate($Day->getTimestamp());
			if (false === $days_events || !is_array($days_events)) {
				die('LCC_View ERROR: Failed rendering Calendar, EventDriver dropped an error!');
			} else {
				// fetch data for all events of that day if not already fetched
				foreach ($days_events as $id) {
					if (!array_key_exists($id, $fetched_events)) {
						$fetched_events[$id] = $eventDriver->getAllData($id);
					}
					
					// Store the events data as reference if data is valid
					if (is_array($fetched_events[$id])) {
						$current_day_data['events'][$id] =& $fetched_events[$id];
					}
				}
			}
			
			// Insert this days data into the week row
			$calendar_data[$week_counter][] = $current_day_data;
		}
		
		// Ok, we are finished fetching and processing data.
		// Now we can assign the data to the calendar template
		// The template decides on its own how to exactly render the data


		// Assign calendar metadata
		$this->assign('month', $Month->thisMonth('int'));
		$this->assign('year', $Month->thisYear('int'));
		
		// Store date for later use
		$_SESSION['lcc_year']  = $Month->thisYear('int');
		$_SESSION['lcc_month'] = $Month->thisMonth('int');
		
		// Assign calendar day data
		$this->assign_by_ref('data', $calendar_data);
		
		// Display the calendar
		$this->display('calendar.tpl');
	}
	
	/**
	* SMARTY modificator for displaying the current month name
	*
	* The goal of this function is to provide
	* an easy means to display a month name configurable in the
	* language file in the users language.
	* Because we implement it our own way, we are not bound to setlocale() and friends.
	*
	* @param int $month   Number of month (1-12)
	* @return string      Name of the month based on users language
	*/
	function toMonthString($month)
	{
		if (!is_numeric($month) || $month <= 0 || $month > 12) die('<b>LCC_View ERROR:</b>toMonthString() was unable to get the months name because the month was not numeric');
		return $this->getLang('monthname_'.$month);
	}
	
	/**
	* Converts a timestamp to a user defined date format described in the language file
	*
	* @param int $timestamp
	* @return string
	*/
	function convertDate($timestamp)
	{
		if (is_numeric($timestamp)) {
			$dateformat = $this->getLang('date_format');
			return strftime($dateformat, $timestamp);
		} else {
			return $timestamp;
		}
	}
	
	/**
	* Converts a timestamp to a user defined time format described in the language file
	*
	* @param int $timestamp
	* @return string
	*/
	function convertTime($timestamp)
	{
		if (is_numeric($timestamp)) {
			$dateformat = $this->getLang('time_format');
			return strftime($dateformat, $timestamp);
		} else {
			return $timestamp;
		}
	}
}
?>
Return current item: Lightweight Club Calendar