Initial commit: Kimai TMS

Kimai time management system (PHP 5.6 / MySQL) with custom extensions:
- Budget tracking (ki_budget)
- Change request form (ki_changerequest)
- Invoice generation (ki_invoice)
- Expense tracking (ki_expenses)
- Flexi time (ki_flexitime)
- Export, admin panel, timesheets, tasks, summary

Database: ~900K time entries, 2,000+ users, 439 projects.
Config: includes/autoconf.php — localhost/kimai/kimai
This commit is contained in:
TMS
2026-06-18 21:20:26 +00:00
commit eb299c9131
1099 changed files with 359817 additions and 0 deletions

2
includes/.htaccess Normal file
View File

@@ -0,0 +1,2 @@
order deny,allow
deny from all

View File

@@ -0,0 +1,63 @@
<?php
/**
* This file is part of
* Kimai - Open Source Time Tracking // http://www.kimai.org
* (c) 2006-2009 Kimai-Development-Team
*
* Kimai is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; Version 3, 29 June 2007
*
* Kimai 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kimai; If not, see <http://www.gnu.org/licenses/>.
*/
// from http://de2.php.net/manual/de/function.array-replace-recursive.php#92574
if (!function_exists('array_replace_recursive')) {
function array_replace_recursive($array, $array1)
{
if (!function_exists('recurse')) {
function recurse($array, $array1)
{
foreach ($array1 as $key => $value)
{
// create new key in $array, if it is empty or not an array
if (!isset($array[$key]) || (isset($array[$key]) && !is_array($array[$key])))
{
$array[$key] = array();
}
// overwrite the value in the base array
if (is_array($value))
{
$value = recurse($array[$key], $value);
}
$array[$key] = $value;
}
return $array;
}
}
// handle the arguments, merge one by one
$args = func_get_args();
$array = $args[0];
if (!is_array($array))
{
return $array;
}
for ($i = 1; $i < count($args); $i++)
{
if (is_array($args[$i]))
{
$array = recurse($array, $args[$i]);
}
}
return $array;
}
}
?>

12
includes/autoconf.php Normal file
View File

@@ -0,0 +1,12 @@
<?php
// Kimai autoconf — local dev setup
$server_hostname = "localhost";
$server_database = "kimai";
$server_username = "kimai";
$server_password = "kimai";
$server_conn = "mysql";
$server_type = "";
$server_prefix = "kimai15_";
$language = "en";
$password_salt = "S8047bw9Xdn60q8YPxh7Z";
?>

79
includes/basics.php Normal file
View File

@@ -0,0 +1,79 @@
<?php
/**
* This file is part of
* Kimai - Open Source Time Tracking // http://www.kimai.org
* (c) 2006-2009 Kimai-Development-Team
*
* Kimai is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; Version 3, 29 June 2007
*
* Kimai 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kimai; If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Basic initialization takes place here.
* From loading the configuration to connecting to the database this all is done
* here.
*
* What does NOT happen here is including the database dependant functions.
*/
error_reporting(E_ERROR);
if (!defined('WEBROOT'))
define('WEBROOT', dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR);
require(WEBROOT.'includes/5.3.functions.php');
if (!file_exists(WEBROOT.'includes/autoconf.php')) {
if (preg_match('|core/[^?]*\.php|',$_SERVER['PHP_SELF'])>0)
header('location:../error.php');
else
header('location:error.php');
}
require(WEBROOT.'includes/autoconf.php');
if (!isset($server_hostname)) {
header('location:installer/index.php');
exit;
}
require(WEBROOT.'includes/vars.php');
require(WEBROOT.'includes/func.php');
require(WEBROOT."includes/connect_".$kga['server_conn'].".php");
$vars = var_get_data();
if (!empty($vars)) {
$kga['currency_name'] = $vars['currency_name'];
$kga['currency_sign'] = $vars['currency_sign'];
$kga['show_sensible_data'] = $vars['show_sensible_data'];
$kga['show_update_warn'] = $vars['show_update_warn'];
$kga['check_at_startup'] = $vars['check_at_startup'];
$kga['show_daySeperatorLines'] = $vars['show_daySeperatorLines'];
$kga['show_gabBreaks'] = $vars['show_gabBreaks'];
$kga['show_RecordAgain'] = $vars['show_RecordAgain'];
$kga['show_TrackingNr'] = $vars['show_TrackingNr'];
$kga['date_format'][0] = $vars['date_format_0'];
$kga['date_format'][1] = $vars['date_format_1'];
$kga['date_format'][2] = $vars['date_format_2'];
if ($vars['language'] != '')
$kga['language'] = $vars['language'];
else if ($kga['language'] == '')
$kga['language'] = 'en';
if ($vars['defaultTimezone'])
date_default_timezone_set($vars['defaultTimezone']);
}
// load language file
$kga['lang'] = require(WEBROOT.'language/en.php');
if ($kga['language'] != 'en')
$kga['lang'] = array_replace_recursive($kga['lang'],include(WEBROOT."language/${kga['language']}.php"));
?>

View File

@@ -0,0 +1,74 @@
<?php
/**
* This file is part of
* Kimai - Open Source Time Tracking // http://www.kimai.org
* (c) 2006-2009 Kimai-Development-Team
*
* Kimai is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; Version 3, 29 June 2007
*
* Kimai 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kimai; If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Connect to a mysql database using the MySQL extension in PHP.
*/
// ==================================================================================
// = check for additional database(s) and set $kga['server_database'] accordingly =
// = $kga['server_database'] stays untouched if there is no entry in the =
// = $server_ext_database array (for more info see /includes/vars.php) =
// ==================================================================================
if (isset($_REQUEST['database'])) {
if ($_REQUEST['database']==true) {
$dbnr = $_REQUEST['database'] - 1;
$kga['server_database'] = $server_ext_database[$dbnr];
if ($server_ext_username[$dbnr] != '') {
$kga['server_username'] = $server_ext_username[$dbnr];
}
if ($server_ext_password[$dbnr] != '') {
$kga['server_password'] = $server_ext_password[$dbnr];
}
if ($server_ext_prefix[$dbnr] != '') {
$kga['server_prefix'] = $server_ext_prefix[$dbnr];
}
}
} else {
if (isset($_COOKIE['kimai_db']) && $_COOKIE['kimai_db'] == true) {
$dbnr = $_COOKIE['kimai_db'] - 1;
$kga['server_database'] = $server_ext_database[$dbnr];
if ($server_ext_username[$dbnr] != '') {
$kga['server_username'] = $server_ext_username[$dbnr];
}
if ($server_ext_password[$dbnr] != '') {
$kga['server_password'] = $server_ext_password[$dbnr];
}
if ($server_ext_prefix[$dbnr] != '') {
$kga['server_prefix'] = $server_ext_prefix[$dbnr];
}
}
}
// ===========================================================================
include(WEBROOT."libraries/mysql.class.php");
if (isset($utf8) && $utf8) {
$conn = new MySQL(true, $kga['server_database'], $kga['server_hostname'], $kga['server_username'], $kga['server_password'],"utf-8");
} else {
$conn = new MySQL(true, $kga['server_database'], $kga['server_hostname'], $kga['server_username'], $kga['server_password']);
}
if ($conn->Error()) $conn->Kill();
?>

93
includes/connect_pdo.php Normal file
View File

@@ -0,0 +1,93 @@
<?php
/**
* This file is part of
* Kimai - Open Source Time Tracking // http://www.kimai.org
* (c) 2006-2009 Kimai-Development-Team
*
* Kimai is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; Version 3, 29 June 2007
*
* Kimai 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kimai; If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Connect to a mysql database using the PDO extension.
*/
// ==================================================================================
// = check for additional database(s) and set $kga['server_database'] accordingly =
// = $kga['server_database'] stays untouched if there is no entry in the =
// = $server_ext_database array (for more info see /includes/vars.php) =
// ==================================================================================
if (isset($_REQUEST['database'])) {
if ($_REQUEST['database']==true) {
$dbnr = $_REQUEST['database'] - 1;
$kga['server_database'] = $server_ext_database[$dbnr];
if ($server_ext_username[$dbnr] != '') {
$kga['server_username'] = $server_ext_username[$dbnr];
}
if ($server_ext_password[$dbnr] != '') {
$kga['server_password'] = $server_ext_password[$dbnr];
}
if ($server_ext_prefix[$dbnr] != '') {
$kga['server_prefix'] = $server_ext_prefix[$dbnr];
}
}
} else {
if (isset($_COOKIE['kimai_db']) && $_COOKIE['kimai_db'] == true) {
$dbnr = $_COOKIE['kimai_db'] - 1;
$kga['server_database'] = $server_ext_database[$dbnr];
if ($server_ext_username[$dbnr] != '') {
$kga['server_username'] = $server_ext_username[$dbnr];
}
if ($server_ext_password[$dbnr] != '') {
$kga['server_password'] = $server_ext_password[$dbnr];
}
if ($server_ext_prefix[$dbnr] != '') {
$kga['server_prefix'] = $server_ext_prefix[$dbnr];
}
}
}
// ===========================================================================
$link = @mysql_connect($kga['server_hostname'], $kga['server_username'], $kga['server_password']);
if (!$link) {
logfile("CONNECTION ERROR (STD MYSQL): " . mysql_error());
header("Location: error.php");
exit;
}
if (isset($utf8)) {
if ($utf8) mysql_query("SET CHARACTER SET utf-8");
}
@mysql_select_db($kga['server_database']);
/* Preparing for PDO :-) */
$pdo_dsn = $kga['server_type'] . ':dbname=' . $kga['server_database'] . ';host=' . $kga['server_hostname'];
$pdo_user = $kga['server_username'];
$pdo_password = $kga['server_password'];
try {
$pdo_conn = new PDO($pdo_dsn, $pdo_user, $pdo_password);
} catch (PDOException $pdo_ex) {
logfile('PDO CONNECTION FAILED: ' . $pdo_ex->getMessage());
header("Location: error.php");
}
// logfile("******************** PDO database connection established.");
?>

4596
includes/db_layer_mysql.php Normal file

File diff suppressed because it is too large Load Diff

4077
includes/db_layer_pdo.php Normal file

File diff suppressed because it is too large Load Diff

954
includes/func.php Normal file
View File

@@ -0,0 +1,954 @@
<?php
/**
* This file is part of
* Kimai - Open Source Time Tracking // http://www.kimai.org
* (c) 2006-2009 Kimai-Development-Team
*
* Kimai is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; Version 3, 29 June 2007
*
* Kimai 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kimai; If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Functions defined here are not directly accessing the database.
*/
// Require database layer functions
if (isset($kga['server_conn']))
require("db_layer_".$kga['server_conn'].".php");
/**
* Prepare all values of the array so it's save to put them into an sql query.
* The conversion to utf8 is done here as well, if configured.
*
* @param array $data Array which values are being prepared.
* @return array The same array, except all values are being escaped correctly.
*/
function clean_data($data) {
global $kga;
foreach ($data as $key => $value) {
if ($key != "pw") {
$return[$key] = urldecode(strip_tags($data[$key]));
$return[$key] = str_replace('"','_',$data[$key]);
$return[$key] = str_replace("'",'_',$data[$key]);
$return[$key] = str_replace('\\','',$data[$key]);
} else {
$return[$key] = $data[$key];
}
if ($kga['utf8']) $return[$key] = utf8_decode($return[$key]);
}
return $return;
}
/**
* Kill the current users session by redirecting him to the logout page.
*/
function kickUser() {
die("<script type='text/javascript'>window.location.href = '../index.php?a=logout';</script>");
}
/**
* returns formatted time string -> h:mm
* input: number of seconds
*
* @param integer $sek seconds to extract the time from
* @return string
* @author th
* @deprecated use formatDuration instead
*/
function intervallApos($sek) {
if (is_array($sek)) {
$arr = array();
foreach ($sek as $key=>$value)
{
$arr[$key] = intervallApos($value);
}
return $arr;
}
else
return sprintf('%d:%02d', $sek / 3600, $sek / 60 % 60);
}
/**
* returns formatted time string -> h:mm:ss
* input: number of seconds
*
* @param integer $sek seconds to extract the time from
* @return string
* @author th
* @deprecated use formatDuration instead
*/
function intervallColon($sek) {
return sprintf('%d:%02d:%02d', $sek / 3600, $sek / 60 % 60, $sek % 60);
}
/**
* Format a duration given in seconds according to the global setting. Either
* seconds are shown or not.
*
* @param integer|array one value in seconds or an array of values in seconds
* @return integer|array depending on the $sek param which contains the formatted duration
* @author sl
*/
function formatDuration($sek) {
global $kga;
if (is_array($sek)) {
// Convert all values of the array.
$arr = array();
foreach ($sek as $key=>$value)
{
$arr[$key] = formatDuration($value);
}
return $arr;
}
else {
// Format accordingly.
if ($kga['conf']['durationWithSeconds'] == 0)
return sprintf('%d:%02d', $sek / 3600, $sek / 60 % 60);
else
return sprintf('%d:%02d:%02d', $sek / 3600, $sek / 60 % 60, $sek % 60);
}
}
/**
* Format a currency or an array of currencies accordingly.
*
* @param integer|array one value or an array of decimal numbers
* @return integer|array formatted string(s)
* @author sl
*/
function formatCurrency($number,$htmlNoWrap = true) {
global $kga;
if (is_array($number)) {
// Convert all values of the array.
$arr = array();
foreach ($number as $key=>$value)
{
$arr[$key] = formatCurrency($value);
}
return $arr;
}
else {
$value = str_replace(".", $kga['conf']['decimalSeparator'], sprintf("%01.2f",$number) );
if ($kga['conf']['currency_first'])
$value = $kga['currency_sign']." ".$value;
else
$value = $value." ".$kga['currency_sign'];
if ($htmlNoWrap)
return "<span style=\"white-space: nowrap;\">$value</span>";
else
return $value;
}
}
/**
* Format the annotations and only include data which the user wants to see.
* The array which is passed to the method will be modified.
*
* @param $ann array the annotation array (userid => (time, costs) )
*/
function formatAnnotations(&$ann) {
$type = usr_get_preference('ui.sublistAnnotations');
$userIds = array_keys($ann);
if ($type == null)
$type = 0;
switch ($type) {
case 0:
// just time
foreach ($userIds as $userId) {
$ann[$userId] = formatDuration($ann[$userId]['time']);
}
break;
case 1:
// just costs
foreach ($userIds as $userId) {
$ann[$userId] = formatCurrency($ann[$userId]['costs']);
}
break;
case 2:
default:
// both
foreach ($userIds as $userId) {
$time = formatDuration($ann[$userId]['time']);
$costs = formatCurrency($ann[$userId]['costs']);
$ann[$userId] = "<span style=\"white-space: nowrap;\">$time |</span> $costs";
}
}
}
/**
* returns hours, minutes and seconds as array
* input: number of seconds
*
* @param integer $sek seconds to extract the time from
* @return array
* @author th
*/
function hourminsec($sek) {
$i['h'] = $sek / 3600 % 24;
$i['i'] = $sek / 60 % 60;
$i['s'] = $sek % 60;
return $i;
}
/**
* returns array of subdirectorys - needed for skin selector
*
* @param string $dir Directory to list subdirectorys from
* @return array
* @author unknown
*/
function ls($dir){
$arr_subfolders = array();
$i=0;
$handle = opendir($dir);
while (false !== ($readdir = readdir($handle))) {
if ($readdir != '.' && $readdir != '..' && substr($readdir,0,1) != '.' && is_dir("${dir}/${readdir}") ) {
$arr_subfolders[$i] = $readdir;
$i++;
}
}
return isset($arr_subfolders)?$arr_subfolders:false;
closedir($handle);
}
/**
* returns array of language files
*
* @param none
* @return array
* @author unknown/th
*/
function langs(){
$arr_files = array();
$arr_files[] = "";
$handle = opendir(WEBROOT.'/language/');
while (false !== ($readdir = readdir($handle))) {
if ($readdir != '.' && $readdir != '..' && substr($readdir,0,1) != '.'
&& endsWith($readdir,'.php') ) {
$arr_files[] = str_replace(".php", "", $readdir);
}
}
closedir($handle);
sort($arr_files);
return $arr_files;
}
/**
* Get a list of available time zones. This is directly taken from PHP.
*
* @return array of timezone names
*/
function timezoneList() {
return DateTimeZone::listIdentifiers();
}
/**
* Returns array for smarty's html_options funtion.
*
* <pre>
* returns:
* [0] -> pct/evt names
* [1] -> values as IDs
* </pre>
*
* @param string either 'pct', 'evt', 'knd', 'grp'
* @return array
* @author th, sl, kp
*/
function makeSelectBox($subject,$user,$selection=null){
global $kga;
$sel = array();
$sel[0] = array();
$sel[1] = array();
switch ($subject) {
case 'pct':
$arr_pct = get_arr_pct($user);
$i=0;
foreach ($arr_pct as $pct) {
if ($pct['pct_visible']) {
if ($kga['conf']['flip_pct_display']) {
$sel[0][$i] = $pct['knd_name'] . ": " . $pct['pct_name'];
if ($kga['conf']['pct_comment_flag']) {
$sel[0][$i] .= "(" . $pct['pct_comment'] .")" ;
}
} else {
$sel[0][$i] = $pct['pct_name'] . " (" . $pct['knd_name'] . ")";
if ($kga['conf']['pct_comment_flag']) {
$sel[0][$i] .= "(" . $pct['pct_comment'] .")";
}
}
$sel[1][$i] = $pct['pct_ID'];
$i++;
}
}
break;
case 'evt':
$arr_evt = get_arr_evt($user);
$i=0;
foreach ($arr_evt as $evt) {
if ($evt['evt_visible']) {
$sel[0][$i] = $evt['evt_name'];
$sel[1][$i] = $evt['evt_ID'];
$i++;
}
}
break;
case 'tpl':
$arr_tpl = get_arr_tpl($user);
$i=0;
foreach ($arr_tpl as $tpl) {
$sel[0][$i] = $tpl['tpl_name'];
$sel[1][$i] = $tpl['tpl_ID'];
$i++;
}
break;
case 'knd':
$arr_knd = get_arr_knd($user);
$i=0;
$selectionFound = false;
foreach ($arr_knd as $knd) {
if ($knd['knd_visible']) {
$sel[0][$i] = $knd['knd_name'];
$sel[1][$i] = $knd['knd_ID'];
$i++;
if ($selection == $knd['knd_ID'])
$selectionFound = true;
}
}
if ($selection != null && !$selectionFound) {
$data = knd_get_data($selection);
$sel[0][$i] = $data['knd_name'];
$sel[1][$i] = $data['knd_ID'];
}
break;
case 'grp':
$arr_grp = get_arr_grp();
$i=0;
foreach ($arr_grp as $grp) {
if (!$grp['grp_trash']) {
$sel[0][$i] = $grp['grp_name'];
$sel[1][$i] = $grp['grp_ID'];
$i++;
}
}
break;
default:
// TODO leave default options empty ???
break;
}
return $sel;
}
/**
* returns list of projects and their time summary within zef_entry timespace as array
*
* OLD VERSION THAT MERGES TWO QUERYS - bad bad stuff ...
* TODO: [tom] revise with join query!
*
* @param integer $group ID of group in table grp
* @param integer $user ID of user in table usr
* @param integer $in start time in unix seconds
* @param integer $out end time in unix seconds
* @global array $kga kimai-global-array
* @return array
* @author th
*/
function get_arr_pct_with_time($group,$user,$in,$out) {
global $kga;
//TODO: [tom] Functions results with 1 query
$arr_pcts = get_arr_pct($group);
$arr_time = get_arr_time_pct($user,$in,$out);
//TODO END
$arr = array();
$i=0;
foreach ($arr_pcts as $pct) {
$arr[$i]['pct_ID'] = $pct['pct_ID'];
$arr[$i]['knd_ID'] = $pct['knd_ID'];
$arr[$i]['pct_name'] = $pct['pct_name'];
$arr[$i]['pct_comment'] = $pct['pct_comment'];
$arr[$i]['knd_name'] = $pct['knd_name'];
$arr[$i]['pct_visible'] = $pct['pct_visible'];
$arr[$i]['zeit'] = @formatDuration($arr_time[$pct['pct_ID']]);
$i++;
}
return $arr;
}
/**
* returns a random code with given length
*
* @global integer $length length of the code
* @return array
* @author th
*/
function random_code($length) {
$code = "";
$string="ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijklmnpqrstuvwxyz0123456789";
mt_srand((double)microtime()*1000000);
for ($i=1; $i <= $length; $i++) {
$code .= substr($string, mt_rand(0,strlen($string)-1), 1);
}
return $code;
}
/**
* returns a random number with X digits
*
* @global integer $length digit count of number
* @return array
* @author th
*/
function random_number($length) {
$number = "";
$string="0123456789";
mt_srand((double)microtime()*1000000);
for ($i=1; $i <= $length; $i++) {
$number .= substr($string, mt_rand(0,strlen($string)-1), 1);
}
return $number;
}
/**
* checks if the database structure needs to be updated for new Kimai version.
* if yes the function redirects to /admin/updater.php
*
* @param string $path path to admin dir relative to the document that calls this function (usually "." or "..")
* @global array $kga kimai-global-array
* @return array
* @author th
*/
function checkDBversion($path) {
global $kga;
// check for versions before 0.7.13r96
$installedVersion = get_DBversion();
$checkVersion = $installedVersion[0];
$checkVersion = "$checkVersion";
if ($checkVersion != $kga['version']) {
header("Location: $path/updater.php");
exit;
}
// the check for revision is much simpler ...
if ( (int)$installedVersion[1] < (int)$kga['revision']) {
header("Location: $path/updater.php");
exit;
}
}
/**
* returns browser name
*
* returns: "Opera", "msie", "Safari", "Mozilla" or "?"
*
* TODO: [togi] check if this still is really needed anywhere
* Smarty has a browser value by itself!!!
*
*
* @return string
* @author th
*/
function get_agent() {
@$agent=$_SERVER["HTTP_USER_AGENT"];
if(strpos($agent,"opera") !== false) $browser = "Opera";
else if(strpos($agent,"msie") !== false) $browser = "msie";
else if(strpos($agent,"Safari") !== false) $browser = "Safari";
else if(strpos($agent,"mozilla") !== false) $browser = "Mozilla";
else $browser = "?";
return $browser;
}
/**
* writes errors during install or update to the logfile stored in temporary
*
* @param string $value message
* @param string $path relative path to temporary directory
* @param boolean $success
* @author th
*/
function logfile($value) {
$value = str_replace("\n", "", $value);
$value = str_replace(" ", " ", $value);
$value = str_replace(" ", " ", $value);
$logdatei=fopen(WEBROOT."temporary/logfile.txt","a");
fputs($logdatei, date("[d.m.Y H:i:s] ",time()) . $value ."\n");
fclose($logdatei);
}
/**
* preprocess shortcut for date entries
*
* allowed shortcut formats are shown in the dialogue for edit timesheet entries (click the "?")
*
* @param string $date shortcut date
* @return string
* @author th
*/
function expand_date_shortcut($date) {
$date = str_replace(" ","",$date);
// empty string can't be a time value
if (strlen($date)==0)
return false;
// get the parts
$parts = preg_split("/\./",$date);
if (count($parts) == 0 || count($parts) > 3)
return false;
// check day
if (strlen($parts[0]) == 1)
$parts[0] = "0".$parts[0];
// check month
if (!isset($parts[1]))
$parts[1] = date("m");
else if (strlen($parts[1]) == 1)
$parts[1] = "0".$parts[1];
// check year
if (!isset($parts[2]))
$parts[2] = date("Y");
else if (strlen($parts[2]) == 2) {
if ($parts[2] > 70)
$parts[2] = "19".$parts[2];
else {
if ($parts[2] < 10)
$parts[2] = "200".$parts[2];
else
$parts[2] = "20".$parts[2];
}
}
$return = implode(".",$parts);
if (!preg_match("/([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{2,4})/",$return)) $return = false;
return $return;
}
/**
* preprocess shortcut for time entries
*
* allowed shortcut formats are shown in the dialogue for edit timesheet entries (click the "?")
*
* @param string $date shortcut time
* @return string
* @author th
*/
function expand_time_shortcut($time) {
$time = str_replace(" ","",$time);
// empty string can't be a time value
if (strlen($time)==0)
return false;
// get the parts
$parts = preg_split("/:|\./",$time);
for ($i=0;$i<count($parts);$i++) {
switch (strlen($parts[$i])) {
case 0:
return false;
case 1:
$parts[$i] = "0".$parts[$i];
}
}
// fill unsued parts (eg. 12:00 given but 12:00:00 is needed)
while (count($parts) < 3) {
$parts[] = "00";
}
$return = implode(":",$parts);
$regex23 = '([0-1][0-9])|(2[0-3])'; // regular expression for hours
$regex59 = '([0-5][0-9])'; // regular expression for minutes and seconds
if (!preg_match("/^($regex23):($regex59):($regex59)$/",$return)) $return = false;
return $return;
}
/**
* check if a parset string matches with the following time-formatting: 20.08.2008-19:00:00
* returns true if string is ok
*
* @param string $timestring
* @return boolean
* @author th
*/
function check_time_format($timestring) {
if (!preg_match("/([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{2,4})-([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})/",$timestring)) {
return false; // WRONG format
} else {
$ok = 1;
$hours = substr($timestring,11,2);
$minutes = substr($timestring,14,2);
$seconds = substr($timestring,17,2);
if ((int)$hours>=24) $ok=0;
if ((int)$minutes>=60) $ok=0;
if ((int)$seconds>=60) $ok=0;
logfile("timecheck: ".$ok);
$day = substr($timestring,0,2);
$month = substr($timestring,3,2);
$year = substr($timestring,6,4);
if (!checkdate( (int)$month, (int)$day, (int)$year) ) $ok=0;
logfile("time/datecheck: ".$ok);
if ($ok) {
return true;
} else {
return false;
}
}
}
function convert_time_strings($in,$out) {
$explode_in = explode("-",$in);
$explode_out = explode("-",$out);
$date_in = explode(".",$explode_in[0]);
$date_out = explode(".",$explode_out[0]);
$time_in = explode(":",$explode_in[1]);
$time_out = explode(":",$explode_out[1]);
$time['in'] = mktime($time_in[0], $time_in[1], $time_in[2], $date_in[1], $date_in[0], $date_in[2]);
$time['out'] = mktime($time_out[0],$time_out[1],$time_out[2],$date_out[1],$date_out[0],$date_out[2]);
$time['diff'] = (int)$time['out']-(int)$time['in'];
return $time;
}
/**
* returns last day (..30..31) of given month
*
* @param integer $month
* @param integer $year
* @return integer number of day
*
* @author http://lutrov.com/blog/php-last-day-of-the-month-calculation/
*/
function lastday($month = '', $year = '') {
if (empty($month)) {
$month = date('m');
}
if (empty($year)) {
$year = date('Y');
}
$result = strtotime("{$year}-{$month}-01");
$result = strtotime('-1 second', strtotime('+1 month', $result));
return date('d', $result);
}
/**
* read a cookie or return a default value, if cookie is not set
*
* @param string $cookie_name
* @param mixed $default the value, which will be returned, when the cookie is not set
* @return mixed
*
* @author rvock
*/
function get_cookie($cookie_name, $default=null) {
return isset($_COOKIE[$cookie_name]) ? $_COOKIE[$cookie_name] : $default;
}
/**
* check if there are 0s (erroneuos entries) in the zef data while editing and prevent overwriting old data in this case
*
* @param int $id the id of the entry to be edited
* @param array $zef_data
* @return boolean the return value of the actual editing function
*
* @author Oleg
*/
function check_zef_data($id, $zef_data) {
if (($zef_data['in'] == 0) && ($zef_data['out'] == 0) && ($zef_data['diff'] == 0)) {
$zef_final_data['zef_pctID'] = $zef_data['pct_ID'];
$zef_final_data['zef_evtID'] = $zef_data['evt_ID'];
$zef_final_data['zef_location'] = $zef_data['zlocation'];
$zef_final_data['zef_division'] = $zef_data['division'];
$zef_final_data['zef_trackingnr'] = $zef_data['trackingnr'];
$zef_final_data['zef_comment'] = $zef_data['comment'];
$zef_final_data['zef_comment_type'] = $zef_data['comment_type'];
$zef_final_data['zef_rate'] = $zef_data['rate'];
$zef_final_data['zef_cleared'] = $zef_data['cleared'];
return zef_edit_record($id,$zef_final_data);
} else {
$zef_final_data['zef_pctID'] = $zef_data['pct_ID'];
$zef_final_data['zef_evtID'] = $zef_data['evt_ID'];
$zef_final_data['zef_location'] = $zef_data['zlocation'];
$zef_final_data['zef_division'] = $zef_data['division'];
$zef_final_data['zef_trackingnr'] = $zef_data['trackingnr'];
$zef_final_data['zef_comment'] = $zef_data['comment'];
$zef_final_data['zef_comment_type'] = $zef_data['comment_type'];
$zef_final_data['zef_in'] = $zef_data['in'];
$zef_final_data['zef_out'] = $zef_data['out'];
$zef_final_data['zef_time'] = $zef_data['diff'];
$zef_final_data['zef_rate'] = $zef_data['rate'];
$zef_final_data['zef_cleared'] = $zef_data['cleared'];
return zef_edit_record($id,$zef_final_data);
}
}
// http://www.alfasky.com/?p=20
// This little function will help you truncate a string to a specified
// length when copying data to a place where you can only store or display
// a limited number of characters, then it will append “…” to it showing
// that some characters were removed from the original entry.
function addEllipsis($string, $length, $end='…')
{
if (strlen($string) > $length)
{
$length -= strlen($end); // $length = $length - strlen($end);
$string = substr($string, 0, $length);
$string .= $end; // $string = $string . $end;
}
return $string;
}
// based on http://wiki.jumba.com.au/wiki/PHP_Generate_random_password
function createPassword($length) {
$chars = "234567890abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
$i = 0;
$password = "";
while ($i <= $length) {
$password .= $chars{mt_rand(0,strlen($chars)-1)};
$i++;
}
return $password;
}
function write_config_file($database,$hostname,$username,$password,$db_layer,$db_type,$prefix,$lang,$salt) {
$file=fopen(realpath(dirname(__FILE__)).'/autoconf.php','w');
if (!$file) return false;
$config=<<<EOD
<?php
/**
* This file is part of
* Kimai - Open Source Time Tracking // http://www.kimai.org
* (c) 2006-2009 Kimai-Development-Team
*
* Kimai is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; Version 3, 29 June 2007
*
* Kimai 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kimai; If not, see <http://www.gnu.org/licenses/>.
*/
// This file was automatically generated by the installer
\$server_hostname = "$hostname";
\$server_database = "$database";
\$server_username = "$username";
\$server_password = "$password";
\$server_conn = "$db_layer";
\$server_type = "$db_type";
\$server_prefix = "$prefix";
\$language = "$lang";
\$password_salt = "$salt";
?>
EOD;
fputs($file, $config);
fclose($file);
return true;
}
/**
* Check if the new time values are better than the old once in the array.
*
* @param $bestTime (called by reference)
* Array containing the, until now, best time data
* @param $newStart suggestion for a better start time
* @param $newEnd suggestion for a better end time
* @param $realStart the real start time
* @param $realEnd the real end time
*/
function roundTimespanCheckIfBetter(&$bestTime,$newStart,$newEnd,$realStart,$realEnd) {
$realDuration = $realEnd-$realStart;
$newDuration = $newEnd-$newStart;
if (abs($realDuration-$newDuration) > abs($realDuration - $bestTime['duration'])) {
// new times are definitely worse, as the timespan is furher away from the real duration
return;
}
// still, this might be closer to the real time
if (abs($realStart-$newStart)+abs($realEnd-$newEnd) >= $bestTime['totalDeviation']) {
// it is not
return;
}
// new time is better, update array
$bestTime['start'] = $newStart;
$bestTime['end'] = $newEnd;
$bestTime['duration'] = $newEnd-$newStart;
$bestTime['totalDeviation'] = abs($realStart-$newStart)+abs($realEnd-$newEnd);
}
/**
* Find a beginning and end time whose timespan is as close to
* the real timepsan as possible while being a multiple of $steps (in minutes).
*
* e.g.: 16:07:31 - 17:15:16 is "rounded" to 16:00:00 - 17:15:00
* with steps set to 15
*
*@param $start the beginning of the timespan
*@param $end the end of the timespan
*@param $steps the steps in minutes (has to divide an hour, e.g. 5 is valid while 7 is not)
*
*/
function roundTimespan($start,$end,$steps) {
// calculate how long a steps is (e.g. 15 second steps are 900 seconds long)
$stepWidth=$steps*60;
if ($steps == 0) {
$bestTime = array();
$bestTime['start'] = $start;
$bestTime['end'] = $end;
return $bestTime;
}
// calculate how many seconds we are over the previous full step
$startSecondsOver = $start%$stepWidth;
$endSecondsOver = $end%$stepWidth;
// calculate earlier and later times of full step width
$earlierStart = $start-$startSecondsOver;
$earlierEnd = $end-$endSecondsOver;
$laterStart = $start+($stepWidth-$startSecondsOver);
$laterEnd = $end+($stepWidth-$endSecondsOver);
// assuming the earlier start end end time are the best (likely not always true)
$bestTime = array();
$bestTime['start'] = $earlierStart;
$bestTime['end'] = $earlierEnd;
$bestTime['duration'] = $earlierEnd-$earlierStart;
$bestTime['totalDeviation'] = abs($start-$earlierStart)+abs($end-$earlierEnd);
// check for better start and end times
roundTimespanCheckIfBetter($bestTime,$earlierStart,$laterEnd,$start,$end);
roundTimespanCheckIfBetter($bestTime,$laterStart,$earlierEnd,$start,$end);
roundTimespanCheckIfBetter($bestTime,$laterStart,$laterEnd,$start,$end);
return $bestTime;
}
/**
* get in and out unix seconds of specific user
*
* <pre>
* returns:
* [0] -> in
* [1] -> out
* </pre>
*
* @param string $user ID of user
* @global array $kga kimai-global-array
* @return array
* @author th
*/
// checked
function get_timespace() {
global $kga, $conn;
$timespace = array(null,null);
if (isset($kga['usr'])) {
$timespace[0] = $kga['usr']['timespace_in'];
$timespace[1] = $kga['usr']['timespace_out'];
}
/* database has no entries? */
$mon = date("n"); $day = date("j"); $Y = date("Y");
if (!$timespace[0]) {
$timespace[0] = mktime(0,0,0,$mon,1,$Y);
}
if (!$timespace[1]) {
$timespace[1] = mktime(23,59,59,$mon,$day,$Y);
}
return $timespace;
}
function endsWith($haystack,$needle) {
return strcmp(substr($haystack, strlen($haystack)-strlen($needle)),$needle)===0;
}
?>

95
includes/kspi.php Normal file
View File

@@ -0,0 +1,95 @@
<?php
/**
* This file is part of
* Kimai - Open Source Time Tracking // http://www.kimai.org
* (c) 2006-2009 Kimai-Development-Team
*
* Kimai is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; Version 3, 29 June 2007
*
* Kimai 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kimai; If not, see <http://www.gnu.org/licenses/>.
*/
/**
* The Kimai Standard Processor Initialization.
* This is used by all processor.php files. General setup stuff is done here.
*/
// ==================================
// = implementing standard includes =
// ==================================
require("basics.php");
if (!$isCoreProcessor) {
$datasrc = "config.ini";
$settings = parse_ini_file($datasrc);
$dir_ext = $settings['EXTENSION_DIR'];
}
// =============================
// = Smarty (initialize class) =
// =============================
require_once(WEBROOT . 'libraries/smarty/Smarty.class.php');
$tpl = new Smarty();
if ($isCoreProcessor) {
$tpl->template_dir = WEBROOT . $dir_templates;
$tpl->compile_dir = WEBROOT . 'compile/';
} else {
$tpl->template_dir = WEBROOT . 'extensions/' . $dir_ext . '/' . $dir_templates;
$tpl->compile_dir = WEBROOT . 'extensions/' . $dir_ext . '/' . 'compile/';
}
// ============================================================================================
// = assigning language and config variables / they are needed in all following smarty output =
// ============================================================================================
$usr = checkUser();
$tpl->assign('kga',$kga);
$comment_types = array($kga['lang']['ctype0'],$kga['lang']['ctype1'],$kga['lang']['ctype2']);
// ==================
// = security check =
// ==================
if ( isset($_REQUEST['axAction']) && !is_array($_REQUEST['axAction']) && $_REQUEST['axAction']!="") {
$axAction = strip_tags($_REQUEST['axAction']);
} else {
$axAction = '';
}
$axValue = isset($_REQUEST['axValue']) ? strip_tags($_REQUEST['axValue']) : '';
$id = isset($_REQUEST['id']) ? strip_tags($_REQUEST['id']) : null;
// ============================================
// = initialize currently displayed timespace =
// ============================================
$timespace = get_timespace();
$in = $timespace[0];
$out = $timespace[1];
if (isset($_REQUEST['first_day']))
$in = (int)$_REQUEST['first_day'];
if (isset($_REQUEST['last_day']))
$out = mktime(23,59,59,date("n",$_REQUEST['last_day']),date("j",$_REQUEST['last_day']),date("Y",$_REQUEST['last_day']));
if ($axAction != "reloadLogfile") {
logfile("KSPI axAction (".(array_key_exists('customer',$kga)?$kga['customer']['knd_name']:$kga['usr']['usr_name'])."): " . $axAction);
}
// prevent IE from caching the response
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
?>

60
includes/vars.php Normal file
View File

@@ -0,0 +1,60 @@
<?php
/**
* This file is part of
* Kimai - Open Source Time Tracking // http://www.kimai.org
* (c) 2006-2009 Kimai-Development-Team
*
* Kimai is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; Version 3, 29 June 2007
*
* Kimai 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kimai; If not, see <http://www.gnu.org/licenses/>.
*/
/**
* The Kimai Global Array ($kga) is initialized here. It is used throught
* all functions, processors, etc.
*/
$kga = array();
require('version.php');
// ------------------------------------------------------------------------------------------------------------------------------------------------
//$kga['show_sensible_data'] = 1; // turn this on to display sensible data in the debug/developer extension
// CAUTION - THINK TWICE IF YOU REALLY WANNA DO THIS AND DON'T FORGET TO TURN IT OFF IN A PRODUCTION ENVIRONMENT!!!
// DON'T BLAME US - YOU HAVE BEEN WARNED!
$kga['logfile_lines'] = 100; // number of lines shown from the logfile in debug extension. Set to "@" to display the entire file (might freeze your browser...)
$kga['delete_logfile'] = 1; // can the logfile be cleaned via debug_ext?
$kga['utf8'] = 0; // set to 1 if utf-8 CONVERSION (!) is needed - this is not always the case,
// depends on server preferences
$kga['calender_start'] = "0"; // here you can set a custom start day for the date-picker.
// if this is not set the day of the users first day in the system will be taken
// Format: ... = "DD/MM/YYYY";
// ------------------------------------------------------------------------------------------------------------------------------------------------
// write vars from autoconf.php into kga
$kga['server_prefix'] = $server_prefix; unset($server_prefix);
$kga['server_hostname'] = $server_hostname; unset($server_hostname);
$kga['server_database'] = $server_database; unset($server_database);
$kga['server_username'] = $server_username; unset($server_username);
$kga['server_password'] = $server_password; unset($server_password);
$kga['server_type'] = $server_type; unset($server_type);
$kga['server_conn'] = $server_conn; unset($server_conn);
$kga['language'] = $language; unset($language);
$kga['password_salt'] = $password_salt; unset($password_salt);
?>

8
includes/version.php Normal file
View File

@@ -0,0 +1,8 @@
<?php
$kga['version'] = "0.9.2";
// $kga['revision'] is the sourceforge SVN revision.
$kga['revision'] = '0440';
// when Kimai moved from syncom to sourceforge we had r866 ...
$kga['revision'] = (int)$kga['revision'] += 866;
$kga['status'] = ""; // leave blank if stable ...
?>