<?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;
}
}
}
?>