<?php
/**
* LCC core - class file
*
* 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
*/
// Version of LCC
define('LCC_VERSION', '0.9.4');
/**
* Require LCC stuff that the core needs
*/
require_once dirname(__FILE__).'/lcc_message.class.php'; // message class
require_once dirname(__FILE__).'/lcc_view.class.php'; // View class
require_once dirname(__FILE__).'/lcc_driver.class.php'; // generic Driver all specific drivers will derive from
require_once dirname(__FILE__).'/lcc_userdriver.class.php'; // generic UserDriver the specific drivers will derive from
require_once dirname(__FILE__).'/lcc_eventdriver.class.php'; // generic EventDriver the specific drivers will derive from
require_once dirname(__FILE__).'/lcc_notedriver.class.php'; // generic NoteDriver the specific drivers will derive from
@include_once 'Calendar/Day.php';
class_exists('Calendar') or die('<b>LCC SETUP ERROR:</b> PEAR::Calendar not found! Please make sure that it is properly installed.');
/**
* Start a new Session or restore old one
*/
@session_start();
/**
* This is the core class of LCC.
*
* It manages the application and its configuration.
* Also this represents the access control layer.
*
* Start LCC as following:
* <code>
* $lcc_core = new LCC_Core($core_config);
* $lcc_core->startApplication();
* </code>
* For more information (the above example is very simple) on how to get LCC
* started refer to the documentation!
*/
class LCC_Core
{
/**
* LCCs core configuration
*
* The default config is initialised inside the Constructor of LCC_Core
* to be able to assign some dynamic default values such as secure (full) paths.
*
* @var array
* @access private
*/
var $_config = array();
/**
* LCC Default Access Control List
*
* Syntax: $permission[{KEY}] = array({VALUE}, array(list of groupnames);
* Example: $permission['event_add'] = array(20, array('members','friends'));
* => this allows users in groups 'members' and 'friends' or users with
* equal or more level than 20 to add a new event.
*
* Beware that the syntax can vary, depending on the functionality.
* See the default values here for more information.
*
* @var array
* @access private
* @see setACL()
*/
var $_acl = array(
'calendar_view' => array( 0, array()), // Viewing the calendar
'event_view' => array( 0, array()), // Viewing detail view of events
'event_add' => array(100, array()), // Adding new events
'event_mod' => array(100, array()), // Modify all existing events
'event_mod_own' => array( 0, array()), // Modify own events
'event_del' => array(100, array()), // Delete existing events
'event_del_own' => array( 0, array()), // Delete own events
'event_subscribe_yes' => array( 10, array()), // Subscribe to an event
'event_subscribe_no' => array( 10, array()), // Subscribe to an event
'event_subscribe_unknown' => array( 10, array()), // Subscribe to an event
'event_subscribe_group_yes' => array( 10, array()), // Subscribe group to an event
'event_subscribe_group_no' => array( 10, array()), // Subscribe group to an event
'event_subscribe_group_unknown' => array( 10, array()), // Subscribe group to an event
'note_view' => array( 10, array()), // View notes of events
'note_add' => array( 10, array()), // Add a note to any event
'note_add_own' => array( 10, array()), // Add a note to own events
'note_mod' => array(100, array()), // Modify any note
'note_mod_own' => array( 10, array()), // Modify own note
'note_del' => array(100, array()), // Delete any note
'note_del_own' => array( 10, array()), // Delete own notes
);
/**
* Instance of the UserDriver
*
* This is the place where {@link getUserDriverInstance()}
* will store a instance of the driver for further lookups
*
* @access private
*/
var $_userDriverInstance = false;
/**
* Instance of the EventDriver
*
* This is the place where {@link getEventDriverInstance()}
* will store a instance of the driver for further lookups
*
* @access private
*/
var $_eventDriverInstance = false;
/**
* Instance of the NoteDriver
*
* This is the place where {@link getNoteDriverInstance()}
* will store a instance of the driver for further lookups
*
* @access private
*/
var $_noteDriverInstance = false;
/**
* A reference to a UserDriver object representing the curent login
*
* It is fetched by {@link getAuthedUser()}
*
* @var LCC_UserDriver
* @access private
**/
var $_authedUser = false;
/**
* Instance of LCC_View
*
* The core uses this to perform the output stuff.
* Dont access ist directly but use {@link getViewInstance()}!
* @access private
*/
var $_lccView = false;
/**
* Constructor - start LCC
*
* Use this method to initialize LCC.
* This initializes the default config.
*
* You are able to override some or all of the parts of the default config,
* just pass the parts of the array in $config.
*
* @param array $config (optional) Parts of LCCs configuration as described in the documentation
* @see $_config
*/
function LCC_Core($config = array())
{
// Set LCC Default config
$this->_config = array(
'data_dir' => dirname(__FILE__).'/../data/', // What is the directory to store data in?
'driver_dir' => dirname(__FILE__).'/../drivers/', // Wehre the drivers reside (with trailing slash)
'design_dir' => dirname(__FILE__).'/../designs/', // Wehre the designs reside
'design' => 'default', // What UI to use
'start_of_week' => 1, // What day is the first day of the week?
'caching' => false, // Use smartys caching? (NOT IMPLEMENTED YET)
'guest_ok' => false, // If true, create a guest login if not already logged in
'guest_username' => 'Anonymous', // What username to assign to guest if $guest_ok=true
'action_start' => 'show_calendar' // What LCC action should be called on start?
);
// override default config
$this->setConfig($config);
}
/**
* Returns the version of this LCC instance
*
* @return string
* @static
*/
function getVersion()
{
return LCC_VERSION;
}
/**
* Returns the value for a configuration item of the core
*
* This could be used by drivers, for example.
*
* @param string $key The key in question
* @return array
* @see LCC_Core(), $_config
*/
function getConfig($key)
{
if(array_key_exists($key, $this->_config)){
return $this->_config[$key];
} else {
die('<b>LCC_CORE ERROR:</b> LCC core does not know the configuration key "'.$key.'"!');
}
}
/**
* Set LCC core config
*
* With this method you can overwrite parts of the LCC core
* default config.
* It performes some basic modifications such as ensuring an ending slash
* configuration items specififying a directory
*
* @param array $config parts of config
* @see $config
*/
function setConfig($config = array())
{
foreach ($config as $key => $value) {
if (!is_array($value)) {
if(array_key_exists($key, $this->_config)){
// ensure trailing slashes for directorys
if (($key == 'data_dir' || $key == 'driver_dir' || $key == 'design_dir') && substr($value,-1,1) != '/') {
$value .= '/';
}
if ($key == 'start_of_week' && ($value < 0 || $value > 7)) {
die('<b>LCC_CORE ERROR:</b> Configuration item "star_of_week" must be between 0 and 6!');
}
$this->_config[$key] = $value;
} else {
die('<b>LCC_CORE ERROR:</b> LCC core does not know the configuration key "'.$key.'"!');
}
} else {
die('<b>LCC_CORE ERROR:</b> The value for "'.$key.'" is not allowed to be an array!');
}
}
}
/**
* Loads a driver
*
* This is only for code factorisation. The driver is loaded
* according to the driverType given. This method is called by the driver-specific methods,
* like loadNoteDriver(), loadEventDriver() and so on...
*
* @access private
* @param string $driverType Name of the driver to be loaded. Must be in $_drivers array.
* @param string $driverClass Name of the class of the driver (eg "LCC_EventDriver_File", or just "File")
* @param array $driverConfig Configuration of the driver (see drivers documentation)
* @return DriverInstance References to the loaded driver.
*/
function &_loadDriver($driverType, $driverClass, $driverConfig)
{
$supported_drivers = array('event', 'note', 'user'); // Possible drivers to be loaded
$driver = false;
// checks if driver type is in the drivers list
if ( in_array(strtolower($driverType), $supported_drivers) )
{
if (empty($driverClass)) die('<b>LCC CORE ERROR:</b> Could not load NoteDriver: you must provide a driver name!');
// Correct the driverType if it's lowercase or mistyped
$driverType = ucfirst( strtolower($driverType) );
// Correct if classname is not in long form
if (!preg_match("/^LCC_" . $driverType . "Driver_(.+)/i", $driverClass)){
$driverClass = 'LCC_' . $driverType . 'Driver_'.$driverClass;
}
// Check if Driver File was already loaded
// If not, inlcude it
if (!class_exists($driverClass)) {
if (preg_match("/LCC_" . $driverType . "Driver_(.+)/i", $driverClass, $matches)){
$filename = $this->getConfig('driver_dir').'/' . strtolower($driverType) . '/'.strtolower($matches[1]).'.class.php';
if (is_readable($filename)) {
include_once $filename;
if (!class_exists($driverClass)) {
die('<b>LCC CORE ERROR:</b> Unable to load ' . $driverType . 'Driver "'.$driverClass.'", but class file was found.<br>Ensure
that the filename matches the lowercased driver name! (eg "file.class.php" matches LCC_' . $driverType . 'Driver_File)');
}
} else {
die('<b>LCC CORE ERROR:</b> Unable to load ' . $driverType . 'Driver classfile: "'.$filename.'"<br>Ensure the driver file is there and readable or include it yourself!');
}
} else {
die('<b>LCC CORE ERROR:</b> Could not derive filename of classfile containing ' . $driverType . 'Driver! "'.$driverClass.'" does not start with "LCC_' . $driverType . 'Driver_"!');
}
}
// Initialisation
$driver = new $driverClass();
// Check if class is compatible to core
$driver->_checkDriver();
// store reference to LCC_Core
$driver->lcc_core =& $this;
// invoke user defined init() method if present
if (method_exists($driver, 'init')) {
$driver->init();
}
// overwrite drivers default config
$driver->setConfig($driverConfig);
// invoke user defined configure() method if present
if (method_exists($driver, 'configure')) {
$driver->configure();
}
// Let's pass the freshly made driver to the calling loading method
return $driver;
} else {
die('<b>LCC CORE ERROR:</b> Unauthorized driver type "' . $driverType . '".');
}
return $driver; // as it's unloaded, will return false
}
/**
* Loads a EventDriver so LCC knows how to get/store event data
*
* The EventDriver gives LCC the ability to do lookups on the datasource
* of the selected EventDriver. This way LCC can store the Event data in
* various storage places like files and databases.
* If this method isn't explicitely called, then the File driver will be used.
*
* To ensure compatibility with the core, several checks are performed on the
* eventdriver. If something isn't okay LCC will die.
*
* If the classfile of the driver is not already loaded, this method
* tries to include it from the configured driver directory (see @link $_config).
*
* @param string $driverClass Name of the class of the driver (eg "LCC_EventDriver_File" or just "File")
* @param array $driverConfig Configuration of the driver (see drivers documentation)
* @return LCC_EventDriver Reference to the loaded driver
*/
function &loadEventDriver($driverClass, $driverConfig)
{
$this->_eventDriverInstance = $this->_loadDriver('event', $driverClass, $driverConfig);
return $this->_eventDriverInstance;
}
/**
* Returns a instance to the selected EventDriver
*
* The core uses this instance to do other lookups in the backend.
* If no EventDriver is loaded via {@link loadEventDriver()} then
* the standard "File" driver will be used with its standard configuration.
*
* @return LCC_EventDriver reference to selected EventDriver
*/
function &getEventDriverInstance()
{
if (!is_a($this->_eventDriverInstance, 'LCC_EventDriver')) {
$this->loadEventDriver('LCC_EventDriver_File', array());
}
return $this->_eventDriverInstance;
}
/**
* Loads a NoteDriver so LCC knows how to get/store note data
*
* The NoteDriver gives LCC the ability to do lookups on the datasource
* of the selected NoteDriver. This way LCC can store the notes data in
* various storage places like files and databases.
* If this method isn't explicitely called, then the File driver will be used.
*
* To ensure compatibility with the core, several checks are performed on the
* eventdriver. If something isn't okay LCC will die.
*
* If the classfile of the driver is not already loaded, this method
* tries to include it from the configured driver directory (see @link $_config).
*
* @param string $driverClass Name of the class of the driver (eg "LCC_EventDriver_File" or just "File")
* @param array $driverConfig Configuration of the driver (see drivers documentation)
* @return LCC_NoteDriver Referece to the loaded driver
*/
function &loadNoteDriver($driverClass, $driverConfig)
{
$this->_noteDriverInstance = $this->_loadDriver('note', $driverClass, $driverConfig);
return $this->_noteDriverInstance;
}
/**
* Returns a instance to the selected NoteDriver
*
* The core uses this instance to do other lookups in the backend.
* If no EventDriver is loaded via {@link loadNoteDriver()} then
* the standard "File" driver will be used with its standard configuration.
*
* @return LCC_NoteDriver reference to selected NoteDriver
*/
function &getNoteDriverInstance()
{
if (!is_a($this->_noteDriverInstance, 'LCC_NoteDriver')) {
$this->loadNoteDriver('LCC_NoteDriver_File', array());
}
return $this->_noteDriverInstance;
}
/**
* Loads a UserDriver so LCC knows how to get user/group data
*
* The Userdriver gives LCC the ability to do lookups on the datasource
* of an external application to get knowledge what users are configured
* there, what groups exist and which rights the users have.
*
* To ensure compatibility with the core, several checks are performed on the
* userdriver. If something isn't okay LCC will die.
*
* If the classfile of the driver is not already loaded, this method
* tries to include it from the configured driver directory (see @link $_config).
*
* @param string $driverClass Name of the class of the driver (eg "LCC_UserDriver_Simple" or just "Simple")
* @param array $driverConfig Configuration of the driver (see drivers documentation)
* @return LCC_UserDriver Reference to the loaded driver
*/
function &loadUserDriver($driverClass, $driverConfig)
{
// storing for further reference
$this->_userDriverInstance = $this->_loadDriver('user', $driverClass, $driverConfig);
return $this->_userDriverInstance;
}
/**
* Returns a instance to the loaded UserDriver
*
* The core uses this instance to do other lookups in the backend.
*
* @return LCC_UserDriver reference to loaded UserDriver
*/
function &getUserDriverInstance()
{
if (is_a($this->_userDriverInstance, 'LCC_UserDriver')) {
return $this->_userDriverInstance;
} else {
die('<b>LCC CORE ERROR:</b> No UserDriver loaded! Use "loadUserDriver()" to load an Userdriver!');
}
}
/**
* Set access control list
*
* With this method you can overwrite parts of the default acl.
*
* Some checks are performed so it is ensured that no corrupt stuff is written to
* the internal acl list.
*
* @param string $action Name of the action
* @param int $level What Level is needed for that action
* @param array $groups What groups are needed for that action (no group = empty array)
* @see $_acl, checkRight(), verifyPermission()
*/
function setACL($action, $level, $groups)
{
if (!is_array($groups)) {
die('<b>LCC_CORE ERROR:</b> setACL(): $groups must be an array of groups, even if empty or only one group name!');
}
if (!is_int($level)) {
die('<b>LCC_CORE ERROR:</b> setACL(): $level must be a number!');
}
if (!array_key_exists($action, $this->_acl)){
die('<b>LCC_CORE ERROR:</b> LCC core does not know the ACL action "'.$action.'"!');
}
$this->_acl[$action] = array($level, $groups);
}
/**
* Checks if a user (or the current user) is allowed to perform a specific action.
*
* The permission is granted if both the user has enough level AND
* is a member in the groups needed for that action.
* If the action doesn't require group membership, only
* the level will be evaluated.
*
* The list of actions is visible in {@link $_acl}, or see "configuring_lcc.txt"
*
* @param string $action
* @param string $username if omitted, then it defaults to the currently logged in user.
* @return boolean
*/
function checkRight($action, $username = false)
{
$sufficient_group = false;
$sufficient_level = false;
if (!$username) {
$username = $this->getAuthedUser();
}
if (strlen($username) > 0) {
if (array_key_exists($action, $this->_acl)) {
$userdriver =& $this->getUserDriverInstance();
$level = $userdriver->getLevel($username);
if ($level === false) die('<b>LCC_CORE ERROR:</b> Could not get userlevel for user '.$username);
$groups = $userdriver->getGroups($username);
if ($groups === false) die('<b>LCC_CORE ERROR:</b> Could not get groups for user '.$username);
$level_needed = $this->_acl[$action][0];
$groups_needed = $this->_acl[$action][1];
// check for level
if ($level >= $level_needed) {
// User is higher level than needed
$sufficient_level = true;
}
// check for groups
// privilegue is granted, if either no groups are needet for the action or
// if the user is in one needed group
if (count($groups_needed) > 0) {
foreach ($groups as $group) {
if (in_array($group, $groups_needed)) {
$sufficient_group = true;
break; // No need to go further : the user has the rights.
}
}
} else {
$sufficient_group = true;
}
} else {
die('<b>LCC_CORE ERROR:</b> LCC core does not know the ACL action "'.$action.'"!');
}
}
return $sufficient_level && $sufficient_group;
}
/**
* Check right of current authenticated user (Blocking)
*
* This is a convinience shorthand for {@link checkRight()}.
* If privilegues are not sufficient, a nice error message will be printed
* and script execution stopped.
*
* @param string $action
* @return true True or end of script execution
*/
function verifyPermission($action)
{
$username = $this->getAuthedUser();
$view =& $this->getViewInstance();
if (!$this->checkRight($action, $username)) {
$msg = new LCC_Message($view->getLang('lcc_error_missing_privilegues'), 'status_failed');
$view =& $this->getViewInstance();
$view->addMessage($msg);
$view->render();
exit;
} else {
return true;
}
}
/**
* Returns the username of the currently logged in user (Blocking)
*
* This method tries to fetch the username of the currently logged in user.
* It performes this task by calling the fetchLogin() method of the
* selected userDriver.
* If something went wrong in the driver, two different things can happen:
* 1) If the core is configured to create a guest login, it will do so.
* Configuration items are: 'guest_ok', 'guest_username'
* The guest account will be created using the {@link LCC_UserDriver_Simple "simple" userdriver}
* with level = 0 and no groups assigned.
*
* 2) the method prints an error message and exits.
*
* If "non blocking" mode is requested, then FALSE will be returned on error.
* This mode gets used by the view instance to assign the username to the templates
*
* @param bool $non_blocking if set to true, no error will be printed but FALSE returned on error
* @return string
*/
function getAuthedUser($non_blocking = false)
{
if (!$this->_authedUser) {
$userdriver =& $this->getUserDriverInstance();
$username = $userdriver->fetchLogin();
if ($username === false || !is_string($username) || strlen($username) == 0) {
// Failed getting username - should we instanciate a guest account?
if ($this->getConfig('guest_ok')) {
$guest_config = array(
'username' => $this->getConfig('guest_username')
);
$this->loadUserDriver('Simple', $guest_config);
// try to fetch username to be sure everything worked okay:
$username = $userdriver->fetchLogin();
if ($username === false || !is_string($username) || strlen($username) == 0) {
if (!$non_blocking) {
$view =& $this->getViewInstance();
$msg = new LCC_Message('<b>Error creating guest login!</b><br>'.$view->getLang('lcc_error_missing_login'), 'status_failed');
$view->addMessage($msg);
$view =& $this->getViewInstance();
$view->render();
exit;
}
} else {
$this->_authedUser = $username;
}
} else {
if (!$non_blocking) {
$view =& $this->getViewInstance();
$msg = new LCC_Message($view->getLang('lcc_error_missing_login'), 'status_failed');
$view->addMessage($msg);
$view =& $this->getViewInstance();
$view->render();
exit;
}
}
} else {
$this->_authedUser = $username;
}
}
return $this->_authedUser; // false by default, and if unchanged within 'if' statement. Otherwise, will be the username.
}
/**
* Returns a reference to the LCC_View instance
*
* If no instance is found so far, a new one will be created.
* The instance is configured via the LCC_Core, but since this
* returns a reference, you can take the instance and modify it.
* But be sure to get it via the "&" operator!
*
* @return LCC_View
*/
function &getViewInstance()
{
if (!is_a($this->_lccView, 'LCC_View')) {
$this->_lccView = new LCC_View($this);
}
return $this->_lccView;
}
/**
* Print out the style definition for the selected Design
*
* This method does just route this call to the LCC_View instance,
* which is truly responsible for output stuff
*/
function printCSS()
{
$view =& $this->getViewInstance();
$view->printCSS();
}
/*
* Following: All Actions LCC can perform
*
* This Actions are called through the selected Design
* (UI = UserInterface) and routet to the core via calling
* startApplication();
*/
/**
* Execute LCC Action
*
* This retriggers {@link startApplication} to enable you to
* perform different tasks from the templates.
* You can use this for example, to display the notes
* if the user looks at the details of an event
*
* @param string $lcc_action LCC Action that should be performed
* @param array $parms Optional parameters for the action, $key=>$value pairs
* @param boolean $direct_rendering Enable/disable direct rendering (you should not need to set this)
*/
function performAction($lcc_action, $parms = array(), $direct_rendering = true)
{
if (is_string($lcc_action) && !empty($lcc_action)) {
$oldget = $_GET;
$_GET = array();
$_GET['lcc_action'] = $lcc_action;
foreach ($parms as $arg => $value) {
if (!is_array($arg) && !is_array($value)) {
$_GET[$arg] = $value;
} else {
die('LCC_CORE ERROR: performAction() can only use strings as parameters!');
}
}
if ($direct_rendering) {
$view =& $this->getViewInstance();
$view->setDirectRendering($direct_rendering);
$this->startApplication();
$_GET = $oldget;
$view->setDirectRendering(false);
return $view->flushDirectRenderingCache(); // return what was rendered dircetly
} else {
$this->startApplication();
$_GET = $oldget;
}
}
}
/**
* Application handling
*
* Perform Actions requested by the user and print out
* the page that results from this requests.
* This method should be invoked by the external php script that
* initializes LCC.
*/
function startApplication()
{
$view =& $this->getViewInstance();
$eventDriver =& $this->getEventDriverInstance();
$noteDriver =& $this->getNoteDriverInstance();
$userDriver =& $this->getUserDriverInstance();
$authed_user =& $this->getAuthedUser();
// Get actions, that need to be performed
switch ($_GET['lcc_action']) {
/*
* Show detail view of a event
*/
case 'show_event':
$this->verifyPermission('event_view');
if (isset($_GET['lcc_id']) && !empty($_GET['lcc_id'])) {
// ID was passed; see if the ID exists
$event_data = $eventDriver->getAllData($_GET['lcc_id']);
if ($event_data === false) {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
} else {
$view->printEventDetails($event_data);
}
} else {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
}
break;
/*
* Show notes of event
*/
case 'show_notes':
$this->verifyPermission('note_view');
if (isset($_GET['lcc_eventid']) && !empty($_GET['lcc_eventid'])) {
// ID was passed; see if the ID exists
$event_data = $eventDriver->getAllData($_GET['lcc_eventid']);
if ($event_data === false) {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
} else {
$notes = $noteDriver->getNotes($event_data['id']);
$notes_data = array();
if (!is_array($notes)) {
die('<b>LCC_Core action show_notes:</b> There was a error in the selected NoteDriver fetching the notes for an event!');
} else {
// resolve notes data
foreach ($notes as $id) {
$note_data = $noteDriver->getAllData($id);
if ($note_data === false) {
die('<b>LCC_Core action show_notes:</b> There was a error in the selected NoteDriver fetching the datafor a note!');
} else {
$notes_data[$note_data['id']] = $note_data;
}
}
$view->printNotes($notes_data);
}
}
} else {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
}
break;
/*
* Delete existing events
*/
case 'delete_event':
if (isset($_GET['lcc_id']) && !empty($_GET['lcc_id'])) {
// ID was passed; see if the ID exists
$event_data = $eventDriver->getAllData($_GET['lcc_id']);
if ($event_data === false) {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
} else {
// Evaluate ACL
// check if we got global deletion right or
// if we are allowed to delete our own event, if this is our own event
if (!$this->checkRight('event_del')) {
// no global deletion right; test if this is our event
if ($event_data['author'] === $authed_user) {
$this->verifyPermission('event_del_own');
} else {
$this->verifyPermission('event_del'); // this drops a error since we dont have this right
}
}
// We have the permission to delete events
// First, let's delete the attached notes!
$notes = $noteDriver->getNotes($_GET['lcc_id']);
// Loops through the event notes. If none, then there won't be any turn.
if (false !== $notes) {
foreach ($notes as $note_id) {
if ( !$noteDriver->deleteNote($note_id) ) {
// Displays a message only on error, otherwise, cleanup quietly
$msg = new LCC_Message($view->getLang('lcc_error_note_deletion', 'status_failed'));
$view->addMessage($msg);
}
}
}
$notes = $noteDriver->getNotes($_GET['lcc_id']); // The array should be empty by now.
if (empty($notes) && $eventDriver->deleteEvent($_GET['lcc_id'])) {
// Show msg and display calendar view
$msg = new LCC_Message($view->getLang('lcc_success_event_deletion'), 'status_ok');
$view->addMessage($msg);
$view->printMonthCalendar();
} else {
// Show msg and event data again
$msg = new LCC_Message($view->getLang('lcc_error_event_deletion'), 'status_failed');
$view->addMessage($msg);
$this->verifyPermission('event_view');
$view->printEventDetails($event_data);
}
}
} else {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
}
break;
/*
* Edit event handles adding new events and modifying old ones
*/
case 'edit_event':
// See, if we need to create a new event
// (by default, we assume it's a new one)
$write_to_id = null;
if (isset($_GET['lcc_id']) && !empty($_GET['lcc_id'])) {
// ID was passed; see if the ID exists
$event_data = $eventDriver->getAllData($_GET['lcc_id']);
if ($event_data !== false) {
$write_to_id = $_GET['lcc_id']; // use provided id
} else {
// drop error since event does not exist
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
break;
}
}
// Check ACL Security for action
// and assign old event data if necessary
if (null === $write_to_id) {
$this->verifyPermission('event_add');
$view->assign('data', false);
} else {
// check if we got global modification right or
// if we are allowed to modify our own event, if this is our own event
if (!$this->checkRight('event_mod')) {
// no global mod right; test if this is our event
if ($event_data['author'] === $authed_user) {
$this->verifyPermission('event_mod_own');
} else {
$this->verifyPermission('event_mod'); // this drops a error since we dont have this right
}
}
$view->assign_by_ref('data', $event_data);
}
// check what needs to be done now
// if we got new data, we need to apply it to the backend,
// if not, we need to display the editable view
if (isset($_POST['lcc_new_data']) && is_array($_POST['lcc_new_data'])) {
// Trim values
foreach ($_POST['lcc_new_data'] as $field => $value) {
$_POST['lcc_new_data'][$field] = trim($value);
}
// check for mandatory fields:
$mandatory_errors = $eventDriver->checkMandatory($_POST['lcc_new_data']);
// check for write protection and syntax:
$validation_errors = array();
foreach ($_POST['lcc_new_data'] as $field => $value) {
if ($eventDriver->checkField($field, 'writable')) {
// Field is writable, check syntax
if (!$eventDriver->checkField($field, 'validate', $value)) {
array_push($validation_errors, $field); // mark this field as not-okay
}
} else {
// We simply ignore this field at update if it isn't writable
// The design should take care to set the field to "disabled"
// because the users won't recognize its readonly state perhaps
unset($_POST['lcc_new_data'][$field]);
}
}
// check for date/time mismatch if fields are validated:
// otherwise date conversion may drop an error
$mismatch_errors = array();
if (count($validation_errors) == 0 && count($mandatory_errors) == 0) {
// Support for german date syntax dd.mm.yyyy, which is not understood by strtotime()
if (preg_match('/^(\d\d?)\.(\d\d?)\.(\d{4})$/', $_POST['lcc_new_data']['start_date'], $matches)) {
$_POST['lcc_new_data']['start_date'] = $matches[3].'-'.$matches[2].'-'.$matches[1];
}
if (preg_match('/^(\d\d?)\.(\d\d?)\.(\d{4})$/', $_POST['lcc_new_data']['end_date'], $matches)) {
$_POST['lcc_new_data']['end_date'] = $matches[3].'-'.$matches[2].'-'.$matches[1];
}
// Convert to timestamps for easier validation
// Times are relative to dates, so always bigger or equal
$date_start = strtotime($_POST['lcc_new_data']['start_date']);
$date_end = strtotime($_POST['lcc_new_data']['end_date']);
$time_start = strtotime($_POST['lcc_new_data']['start_time'], $date_start);
$time_end = strtotime($_POST['lcc_new_data']['end_time'], $date_end);
// Validate conversion to timestamp
if ($date_start >= 0 && $date_end >= 0 && $time_start >= 0 && $time_end >= 0 ) {
// Validate logical correctness, if timestamp conversion was okay
if ($end_date < $start_date) {
array_push($mismatch_errors, 'end_date'); // mark this field as not-okay
} else {
// Date is okay, validate time, if event start and event end is at the
// same day. Additionally start and end time must be given given
// because time is only informal then
// (non-informal state should be enforced via mandatory field switch)
$sameday = ($date_end - $date_start < 86400) ? true : false;
if ($sameday && $time_start != $time_end && $time_start > $date_start && $time_end > $date_end) {
if ($time_end <= $time_start) {
array_push($mismatch_errors, 'end_time'); // mark this field as not-okay
}
}
}
} else {
// die because of hard-error
die('<b>LCC_Core action edit_event:</b> Date/Time could not be converted to timestamp!
Ensure Proper format for date/time fields using setFieldSecurity()! (must support strtotime())');
}
}
// if there were errors, we need to show them together with the entered data
// else we apply data to the backend and show a ok-message
if (count($mandatory_errors) == 0 && count($validation_errors) == 0 && count($mismatch_errors) == 0) {
// Assign editing metadata
if (null === $write_to_id) {
// new event
$_POST['lcc_new_data']['author'] = $authed_user;
$_POST['lcc_new_data']['created'] = time();
$_POST['lcc_new_data']['last_modified'] = '';
$_POST['lcc_new_data']['last_modified_author'] = '';
$_POST['lcc_new_data']['subscribed_yes'] = '';
$_POST['lcc_new_data']['subscribed_no'] = '';
$_POST['lcc_new_data']['subscribed_unknown'] = '';
} else {
// existing event
$_POST['lcc_new_data']['author'] = $event_data['author'];
$_POST['lcc_new_data']['created'] = $event_data['created'];
$_POST['lcc_new_data']['last_modified'] = time();
$_POST['lcc_new_data']['last_modified_author'] = $authed_user;
$_POST['lcc_new_data']['subscribed_yes'] = $event_data['subscribed_yes'];
$_POST['lcc_new_data']['subscribed_no'] = $event_data['subscribed_no'];
$_POST['lcc_new_data']['subscribed_unknown'] = $event_data['subscribed_unknown'];
}
// Assign converted date data to data,
// in order to store timestamps in the backend
$_POST['lcc_new_data']['start_date'] = $date_start;
$_POST['lcc_new_data']['end_date'] = $date_end;
// Assign combined event dates so this metadata gets stored
$_POST['lcc_new_data']['event_start'] = $time_start;
$_POST['lcc_new_data']['event_end'] = $time_end;
$written_id = $eventDriver->storeData($write_to_id, $_POST['lcc_new_data']);
if ($written_id === false) {
$msg = new LCC_Message($view->getLang('lcc_error_event_storage'), 'status_failed');
$view->addMessage($msg);
} else {
// looked good, fetch data and assign to template
$new_event_data = $eventDriver->getAllData($written_id);
if ($new_event_data === false) {
// no such event !? probably the eventDriver didn't return the right id after storage?
die('<b>LCC_Core action edit_event:</b> The Event with ID "'.$written_id.'" (returned from the selected EventDriver->storeData()) could not be found in event backend!<br>
Storage probably went okay, but the selected EventDriver "'.get_class($eventDriver).'" looks broken!');
} else {
$view->assign_by_ref('data', $new_event_data);
$msg = new LCC_Message($view->getLang('lcc_success_event_storage'), 'status_ok');
$view->addMessage($msg);
}
}
} else {
// Output errors
// The core makes some dynamic lookups to the language file
// TODO: code factorization.
if (count($mandatory_errors) > 0) {
$errortext = '<b>'.$view->getLang('lcc_error_field_mandatory').'</b>';
foreach ($mandatory_errors as $field) {
$errortext .= $view->getLang('lcc_event_desc_'.$field).', ';
}
$msg = new LCC_Message(substr($errortext, 0, -2), 'status_failed');
$view->addMessage($msg);
}
if (count($validation_errors) > 0) {
$errortext = '<b>'.$view->getLang('lcc_error_field_invalid').'</b><br>';
foreach ($validation_errors as $field) {
$errortext .= $view->getLang('lcc_event_desc_'.$field).': '.$view->getLang('lcc_event_msg_'.$field).'<br>';
}
$msg = new LCC_Message(substr($errortext, 0, -4), 'status_failed');
$view->addMessage($msg);
}
if (count($mismatch_errors) > 0) {
foreach ($mismatch_errors as $field) {
$errortext .= $view->getLang('lcc_event_desc_'.$field).': '.$view->getLang('lcc_error_date_mismatch').'<br>';
}
$msg = new LCC_Message(substr($errortext, 0, -4), 'status_failed');
$view->addMessage($msg);
}
}
}
$view->display('event_editable.tpl');
break;
/*
* Subscribe yourself to an event or change subscription
*/
case 'event_subscribe':
if (isset($_GET['lcc_id']) && !empty($_GET['lcc_id']) &&
isset($_GET['lcc_interest']) && !empty($_GET['lcc_interest'])) {
// firtstly check permission for that interest
switch ($_GET['lcc_interest']) {
case 'yes':
$this->verifyPermission('event_subscribe_yes');
break;
case 'no':
$this->verifyPermission('event_subscribe_no');
break;
case 'unknown':
$this->verifyPermission('event_subscribe_unknown');
break;
default:
die('<b>LCC_Core action event_subscribe:</b> Illegal interest selected!');
}
// ID was passed; see if the ID exists
$event_data = $eventDriver->getAllData($_GET['lcc_id']);
if ($event_data === false) {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
} else {
$result = $eventDriver->subscribeUser($_GET['lcc_id'], $authed_user, $_GET['lcc_interest']);
if ($result) {
$msg = new LCC_Message($view->getLang('lcc_success_event_subscription'), 'status_ok');
} else {
$msg = new LCC_Message($view->getLang('lcc_error_event_subscription'), 'status_failed');
}
$view->addMessage($msg);
$this->verifyPermission('event_view');
$view->printEventDetails($event_data);
}
} else {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
}
break;
/*
* Subscribe your group members to an event
*/
case 'event_subscribe_group':
if (isset($_GET['lcc_group']) && !empty($_GET['lcc_group']) &&
isset($_GET['lcc_id']) && !empty($_GET['lcc_id']) &&
isset($_GET['lcc_interest']) && !empty($_GET['lcc_interest'])) {
// firtstly check permission for that interest
switch ($_GET['lcc_interest']) {
case 'yes':
$this->verifyPermission('event_subscribe_group_yes');
break;
case 'no':
$this->verifyPermission('event_subscribe_group_no');
break;
case 'unknown':
$this->verifyPermission('event_subscribe_group_unknown');
break;
default:
die('<b>LCC_Core action event_subscribe_group:</b> Illegal interest selected!');
}
// Now check group leadership
$leaded_groups = $userDriver->getGroupsLeaded($authed_user);
if (!in_array($_GET['lcc_group'], $leaded_groups)) {
$msg = new LCC_Message($view->getLang('lcc_error_missing_privilegues'), 'status_failed');
$view->addMessage($msg);
$view->render();
exit;
}
// ID was passed; see if the ID exists
$event_data = $eventDriver->getAllData($_GET['lcc_id']);
if ($event_data === false) {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
} else {
// Fetch group users
$group_users = $userDriver->getGroupMembers($_GET['lcc_group']);
if (is_array($group_users)) {
$errors = 0;
// Subscribe the users
foreach ($group_users as $user) {
$result = $eventDriver->subscribeUser($_GET['lcc_id'], $user, $_GET['lcc_interest']);
if (!$result) {
$errors++;
}
}
if ($errors == 0) {
$msg = new LCC_Message($view->getLang('lcc_success_event_subscription'), 'status_ok');
} else {
$msg = new LCC_Message($view->getLang('lcc_error_event_subscription'), 'status_failed');
}
$view->addMessage($msg);
$this->verifyPermission('event_view');
$view->printEventDetails($event_data);
}
}
} else {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
}
break;
/*
* Edit/Add a note to an event
*
* At least a lcc_eventid must be submitted!
*/
case 'edit_note':
// see, if we need to create a new note
// (by default, we assume it's a new one)
$write_to_id = null;
if (isset($_GET['lcc_id']) && !empty($_GET['lcc_id'])) {
// ID was passed; see if the ID exists
$note_data = $noteDriver->getAllData($_GET['lcc_id']);
if ($note_data !== false) {
$write_to_id = $_GET['lcc_id']; // use provided id
} else {
// drop error since note does not exist
$msg = new LCC_Message($view->getLang('lcc_error_no_such_note'), 'status_failed');
$view->addMessage($msg);
break;
}
}
// See if event exists
// FIXME: duplicated code again :P -> needs to be checked if factorisation is okay
$event_data = false; // as ever, we assume a error by default
if (isset($_GET['lcc_eventid']) && !empty($_GET['lcc_eventid'])) {
$event_data = $eventDriver->getAllData($_GET['lcc_eventid']);
}
if ($event_data === false) {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
$view->render();
return;
}
// Check ACL Security for action
// and assign old note data if necessary
if (null !== $write_to_id) {
if (!$this->checkRight('note_add')) {
// if we didn't have global add right, we
// might be able to add to our own event
if ($event_data['author'] === $authed_user) {
$this->verifyPermission('note_add_own');
} else {
// This will drop a die since we didn't have this right
$this->verifyPermission('note_add');
}
}
} else {
// check if we got global modification right or
// if we are allowed to modify our own note
if (!$this->checkRight('note_mod')) {
// no global mod right; test if this is our note
if ($note_data['author'] === $authed_user) {
$this->verifyPermission('note_mod_own');
} else {
// This will drop a die since we didn't have this right
$this->verifyPermission('note_mod_own');
}
}
}
// check what needs to be done now
// if we got new data, we need to apply it to the backend,
// if not, we need to display the editable view
// TODO: As far as I can tell, this is a copy of the edit_event... So maybe there's a way to
// factorize all (or at least : a large part) of that.
if (isset($_POST['lcc_new_data']) && is_array($_POST['lcc_new_data'])) {
// Trim values
foreach ($_POST['lcc_new_data'] as $field => $value) {
$_POST['lcc_new_data'][$field] = trim($value);
}
// check for mandatory fields:
$mandatory_errors = $noteDriver->checkMandatory($_POST['lcc_new_data']);
// check for write protection and syntax:
$validation_errors = array();
foreach ($_POST['lcc_new_data'] as $field => $value) {
if ($noteDriver->checkField($field, 'writable')) {
// Field is writable, check syntax
if (!$noteDriver->checkField($field, 'validate', $value)) {
array_push($validation_errors, $field); // mark this field as not-okay
}
} else {
// We simply ignore this field at update if it isn't writable
// The design should take care to set the field to "disabled"
// because the users won't recognize its readonly state perhaps
unset($_POST['lcc_new_data'][$field]);
}
}
// check for date/time mismatch if fields are validated:
// otherwise date conversion may drop an error
$mismatch_errors = array();
if (count($validation_errors) == 0) {
// Here for upward compatibility; no date fields are in the users realm
// For validating dates, see edit_event action above
}
// if there were errors, we need to show them together with the entered data
// else we apply data to the backend and show a ok-message
if (count($mandatory_errors) == 0 && count($validation_errors) == 0 && count($mismatch_errors) == 0) {
// Assign editing metadata
$_POST['lcc_new_data']['event_id'] = $event_data['id'];
if (null === $write_to_id) {
$_POST['lcc_new_data']['author'] = $authed_user;
$_POST['lcc_new_data']['created'] = time();
$_POST['lcc_new_data']['last_modified'] = '';
$_POST['lcc_new_data']['last_modified_author'] = '';
} else {
$_POST['lcc_new_data']['author'] = $event_data['author'];
$_POST['lcc_new_data']['created'] = $event_data['created'];
$_POST['lcc_new_data']['last_modified'] = time();
$_POST['lcc_new_data']['last_modified_author'] = $authed_user;
}
$written_id = $noteDriver->storeData($write_to_id, $_POST['lcc_new_data']);
if ($written_id === false) {
$msg = new LCC_Message($view->getLang('lcc_error_note_storage'), 'status_failed');
$view->addMessage($msg);
} else {
// looked good!
// Print message and return to event details of the event
unset($_POST['lcc_new_data']); // clear new data so smarty thinks nothing was submitted
$msg = new LCC_Message($view->getLang('lcc_success_note_storage'), 'status_ok');
$view->addMessage($msg);
$this->verifyPermission('event_view');
$view->printEventDetails($event_data);
}
} else {
// Output errors
// The core makes some dynamic lookups to the language file
if (count($mandatory_errors) > 0) {
$errortext = '<b>'.$view->getLang('lcc_error_field_mandatory').'</b>';
foreach ($mandatory_errors as $field) {
$errortext .= $view->getLang('lcc_note_desc_'.$field).', ';
}
$msg = new LCC_Message(substr($errortext, 0, -2), 'status_failed');
$view->addMessage($msg);
}
if (count($validation_errors) > 0) {
$errortext = '<b>'.$view->getLang('lcc_error_field_invalid').'</b><br>';
foreach ($validation_errors as $field) {
$errortext .= $view->getLang('lcc_note_desc_'.$field).': '.$view->getLang('lcc_note_msg_'.$field).'<br>';
}
$msg = new LCC_Message(substr($errortext, 0, -4), 'status_failed');
$view->addMessage($msg);
}
if (count($mismatch_errors) > 0) {
foreach ($mismatch_errors as $field) {
$errortext .= $view->getLang('lcc_note_desc_'.$field).': '.$view->getLang('lcc_error_date_mismatch').'<br>';
}
$msg = new LCC_Message(substr($errortext, 0, -4), 'status_failed');
$view->addMessage($msg);
}
$view->printNoteForm($note_data);
}
} else {
// just display editable view
$view->printNoteForm($note_data);
}
break;
/*
* Delete existing notes
*/
case 'delete_note':
if (isset($_GET['lcc_id']) && !empty($_GET['lcc_id'])) {
// ID was passed; see if the ID exists
$note_data = $noteDriver->getAllData($_GET['lcc_id']);
if ($note_data === false) {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_note'), 'status_failed');
$view->addMessage($msg);
} else {
// Evaluate ACL
// check if we got global deletion right or
// if we are allowed to delete our own event, if this is our own event
if (!$this->checkRight('note_del')) {
// no global deletion right; test if this is our event
if ($note_data['author'] === $authed_user) {
$this->verifyPermission('note_del_own');
} else {
// Drop an error since we have no mod right
$this->verifyPermission('note_del');
}
}
// See if event exists
// TODO: Check if code factorisation is possible
if (isset($_GET['lcc_eventid']) && !empty($_GET['lcc_eventid'])) {
$event_data = $eventDriver->getAllData($_GET['lcc_eventid']);
if ($event_data === false) {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
$view->render();
return;
}
} else {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_event'), 'status_failed');
$view->addMessage($msg);
$view->render();
return;
}
// TODO: Factorize. (love that).
if ($noteDriver->deleteNote($_GET['lcc_id'])) {
// Show msg and display calendar view
$msg = new LCC_Message($view->getLang('lcc_success_note_deletion'), 'status_ok');
$view->addMessage($msg);
$this->verifyPermission('event_view');
$view->printEventDetails($event_data);
} else {
// Show msg and event data again
$msg = new LCC_Message($view->getLang('lcc_error_note_deletion'), 'status_failed');
$view->addMessage($msg);
$this->verifyPermission('event_view');
$view->printEventDetails($event_data);
}
}
} else {
$msg = new LCC_Message($view->getLang('lcc_error_no_such_note'), 'status_failed');
$view->addMessage($msg);
}
break;
/*
* Display calendar
*/
case 'show_calendar':
// Just print out the calendar view with the selected dates (if set)
$this->verifyPermission('calendar_view');
$view->printMonthCalendar();
break;
/*
* Default action
*/
default:
// perform default action
return $this->performAction($this->_config['action_start'], array(), false);
}
// Finally send HTML to the client
$view->render();
}
/**
* Get upcoming events for the next X days
*
* This method can be used to get the id's of the events
* starting the next X days.
*
* @param int $days Number of days to look forward
* @param int $limit (optional) limit events returned (0=unlimited)
* @param string $date (optional) ISO-8601 format (YYYY-MM-DD) determining the start date, if not set, defaults to TODAY
* @return array Array containing the data of the events. Array keys are the Event IDs
* @todo maybe we should write a method printUpcomingEvents() that uses a template for this, so end users need not to echo the stuff and build links themselves
*/
function getUpcomingEvents($days, $limit = 0, $date = null)
{
$return = array();
$eventDriver =& $this->getEventDriverInstance();
// Ensure $limit and $days is correct
if ($limit < 0) $limit = 0;
if ($days < 0) $days = 0;
if (!$date) $date = date("Y-m-d", time());
// We use PEAR::Calendar to make day shifting more easy and reliable
$day = new Calendar_Day(0, 0, 0);
$day->setTimestamp(strtotime($date) - 86400); // minus one day because we use "next day" in the while loop
// Now loop through the days and get the IDs of the events for the specific days
while (($day = $day->nextDay('object')) && ($limit == 0 || count($return) < $limit) && $days > 0) {
$days--;
$events_ids = $eventDriver->getEventsForDate($day->getTimestamp());
if ($events_ids === false || !is_array($events_ids)) {
die('LCC_CORE ERROR: Unable to get Events for Date! EventDriver dropped an error.');
} else {
// fetch data for events
foreach ($events_ids as $id) {
if (count($return) < $limit || $limit == 0) {
$event_data = $eventDriver->getAllData($id);
// If returned data is invalid, do nothing
if (is_array($event_data) && count($event_data) > 0) {
$return[$id] = $event_data;
}
}
}
}
}
return $return;
}
/**
* Function to build parameters for actions suitable for $LCC_CORE->performAction()
*
* This is neccessary because SMARTY does not support the array() syntax.
*
* Call the function the following way:
* {$LCC_CORE->performAction('action_name', $LCC_CORE->buildActionParams(p_key1, p_val1, p_key2, p_val2, ...))}
*
* @return array
*/
function buildActionParams()
{
$args = func_get_args();
if (count($args) % 2 > 0) die('buildActionParms() wrong parameter count');
$param_list = array();
$param = null;
foreach ($args as $arg) {
if ($param === null) {
$param = $arg;
} else {
$param_list[$param] = $arg;
$param = null;
}
}
return $param_list;
}
}
?>