diff --git a/.gitignore b/.gitignore index 9bfa047e7..d81178e11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ config.php WEB-INF/templates_c/*.* +WEB-INF/templates_c/import_* +WEB-INF/templates_c/tt* WEB-INF/lib/tcpdf/ nbproject/ upload/ @@ -7,3 +9,5 @@ upload/ Thumbs.db *.DS_Store *~ +.idea/ +api/ diff --git a/.htaccess b/.htaccess index c91ca1c3e..3b6168c75 100644 --- a/.htaccess +++ b/.htaccess @@ -1 +1,16 @@ AddDefaultCharset utf-8 + +# Restrict access to Time Tracker only from certain IPs. +# +# See https://www.anuko.com/time-tracker/faq/restrict-access-by-ip.htm for help. +# For this to work make sure AllowOverride is set to All in web server config file. +# Uncomment 3 lines below and set your IP accordingly. +# +# Order Deny,Allow +# Deny from all +# Allow from 127.0.0.1 +# +# An example for an entire subnet 192.168.1.0 - 192.168.1.255. +# Order Deny,Allow +# Deny from all +# Allow from 192.168.1 diff --git a/2fa.php b/2fa.php new file mode 100644 index 000000000..a7e3dbb29 --- /dev/null +++ b/2fa.php @@ -0,0 +1,64 @@ +getParameter('login'); +if ($cl_login == null && $request->isGet()) $cl_login = @$_COOKIE[LOGIN_COOKIE_NAME]; +$cl_password = $request->getParameter('password'); +$cl_auth_code = $request->getParameter('auth_code'); + +$form = new Form('twoFactorAuthForm'); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login)); +$form->getElement('login')->setEnabled(false); +$form->addInput(array('type'=>'password','maxlength'=>'50','name'=>'password','value'=>$cl_password)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'auth_code','value'=>$cl_auth_code)); +$form->addInput(array('type'=>'submit','name'=>'btn_login','value'=>$i18n->get('button.login'))); + +if ($request->isPost()) { + // Validate user input. + if (!ttValidString($cl_login)) $err->add($i18n->get('error.field'), $i18n->get('label.login')); + if (!ttValidString($cl_password)) $err->add($i18n->get('error.field'), $i18n->get('label.password')); + if (!ttValidString($cl_auth_code)) $err->add($i18n->get('error.field'), $i18n->get('form.2fa.2fa_code')); + + if ($err->no()) { + // Get user id. + $user_id = ttUserHelper::getUserIdByTmpRef($cl_auth_code); + if (!$user_id) + $err->add($i18n->get('error.2fa_code')); + + if ($err->no()) { + // Additionally check user password for better protection + // against brute force attacks guessing 2FA codes. + $user = new ttUser(null, $user_id); // Note: reusing $user from initialize.php. + // Check user password. + if (!$auth->doLogin($user->login, $cl_password)) + $err->add($i18n->get('error.auth')); + } + + if ($err->no()) { + // Redirect, depending on user role. + if ($user->isClient()) { + header('Location: reports.php'); + } else { + header('Location: time.php'); + } + exit(); + } + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.twoFactorAuthForm.auth_code.focus()"'); +$smarty->assign('title', $i18n->get('title.2fa')); +$smarty->assign('content_page_name', '2fa.tpl'); +$smarty->display('index.tpl'); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..58c971801 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,32 @@ +# Resources + +* [docs](https://www.anuko.com/time-tracker/features.htm) - detailed documentation about this project (needs updating). +* [forum](https://www.anuko.com/forum/viewforum.php?f=4) - general discussion. + + +# Reporting Bugs + +* GitHub users: create a [new issue](https://github.com/anuko/timetracker/issues) here. +* Forum users: post a [new topic](https://www.anuko.com/forum/viewforum.php?f=4) here. +* Or, send us a [message](https://www.anuko.com/contact.htm). + + +# Reporting Security Issues + +* Use the [contact form](https://www.anuko.com/contact.htm) to report a vulnerability. +* Or send an encrypted email to security_at_anuko_dot_com. Public key to be published soon. + + +# Setting up a Dev Environment + +Docker users: install both docker and docker-compose, then run a dev instance: +```bash +docker-compose up +``` +Navigate to: http://localhost:8080 to use Time Tracker. Default credentials for initial login are: +``` +usr: admin +psw: secret +``` + +Without docker, perform a manual install of a web server, php, database server, and Time Tracker. Full installation and setup guide can be found [here](https://www.anuko.com/time-tracker/install-guide/index.htm). diff --git a/README.md b/README.md index 3822b4c97..022c0c030 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ # Anuko Time Tracker ## About -Anuko [Time Tracker](https://www.anuko.com/time_tracker/index.htm) is a simple, easy to use, open source, web-based time tracking application written in PHP. It allows you to track the time that employees, colleagues, or teammates spend working on projects and tasks. It needs a web server such as Apache, IIS, etc. to run on, and a database to keep the data in, such as MySQL. +Anuko [Time Tracker](https://www.anuko.com/time-tracker/index.htm) is an open source, web-based time tracking application written in PHP. It allows you to track the time that employees or colleagues spend working on projects and tasks. It needs a web server such as Apache, IIS, etc. to run on, and a database to keep the data in, such as MySQL. + +## Terminal Illness of the Owner +December 28, 2023: Nik Okuntseff, the owner and lead software developer at Anuko, developed a terminal illness. Anuko, the company behind Time Tracker, will cease to exist at soon. ## Free Hosting -[Anuko](https://www.anuko.com) provides [free hosting](https://www.anuko.com/time_tracker/free_hosting/index.htm) of Time Tracker to individuals and small teams up to 5 users. To start using Time Tracker immediately, create a team at https://timetracker.anuko.com +[Anuko](https://www.anuko.com) provides [free hosting](https://www.anuko.com/time-tracker/free-hosting/index.htm) of Time Tracker to individuals and small groups up to 5 users. To start using Time Tracker immediately, create a group at https://timetracker.anuko.com ## Resources -* Project home page: https://www.anuko.com/time_tracker/index.htm +* Project home page: https://www.anuko.com/time-tracker/index.htm * Forum: https://www.anuko.com/forum/viewforum.php?f=4 -* Mailing list: http://mailman.anuko.com/mailman/listinfo/timetracker -* How to contribute: https://www.anuko.com/time_tracker/contribute.htm +* Info for developers: https://www.anuko.com/time-tracker/info-for-developers.htm +* How to contribute: https://www.anuko.com/time-tracker/contribute.htm diff --git a/WEB-INF/config.php.dist b/WEB-INF/config.php.dist index 9f53f9dff..6b6a68d84 100644 --- a/WEB-INF/config.php.dist +++ b/WEB-INF/config.php.dist @@ -1,30 +1,6 @@ '); +define('SENDER', 'Anuko Time Tracker '); // MAIL_MODE - mail sending mode. Can be 'mail' or 'smtp'. // 'mail' - sending through php mail() function. // 'smtp' - sending directly through SMTP server. -// See https://www.anuko.com/time_tracker/install_guide/mail.htm +// See https://www.anuko.com/time-tracker/install-guide/mail.htm // define('MAIL_MODE', 'smtp'); define('MAIL_SMTP_HOST', 'localhost'); // For gmail use 'ssl://smtp.gmail.com' instead of 'localhost' and port 465. @@ -143,26 +105,6 @@ define('DEFAULT_CSS', 'default.css'); define('RTL_CSS', 'rtl.css'); // For right to left languages. -// Default date format. Behaviour with not included formats is undefined. Possible values: -// '%Y-%m-%d' -// '%m/%d/%Y' -// '%d.%m.%Y' -// '%d.%m.%Y %a' -define('DATE_FORMAT_DEFAULT', '%Y-%m-%d'); - - -// Default time format. Behaviour with not included formats is undefined. Possible values: -// '%H:%M' -// '%I:%M %p' -define('TIME_FORMAT_DEFAULT', '%H:%M'); - - -// Default week start day. -// Possible values: 0 - 6. 0 means Sunday. -// -define('WEEK_START_DEFAULT', 0); - - // Default language of the application. // Possible values: en, fr, nl, etc. Empty string means the language is defined by user browser. // @@ -191,34 +133,50 @@ define('REPORT_FOOTER', true); define('AUTH_MODULE', 'db'); // LDAP authentication examples. -// Go to https://www.anuko.com/time_tracker/install_guide/ldap_auth/index.htm for detailed configuration instructions. +// Go to https://www.anuko.com/time-tracker/install-guide/ldap-auth/index.htm for detailed configuration instructions. // Configuration example for OpenLDAP server: // define('AUTH_MODULE', 'ldap'); // $GLOBALS['AUTH_MODULE_PARAMS'] = array( -// 'server' => '127.0.0.1', // OpenLDAP server address or name. +// 'server' => '127.0.0.1', // OpenLDAP server address or name. For secure LDAP use ldaps://hostname:port here. // 'type' => 'openldap', // Type of server. openldap type should also work with Sun Directory Server when member_of is empty. // It may work with other (non Windows AD) LDAP servers. For Windows AD use the 'ad' type. -// 'base_dn' => 'ou=People,dc=example,dc=com', // Base distinguished name in LDAP catalog. +// 'base_dn' => 'ou=People,dc=example,dc=com', // Path of user's base distinguished name in LDAP catalog. +// 'user_login_attribute' => 'uid', // LDAP attribute used for login. // 'default_domain' => 'example.com', // Default domain. +// 'tls_cacertdir' => null, // Path to a directory containing CA certificates for secure ldap. +// 'tls_cacertfile' => null, // CA certificate file name for secure ldap. // 'member_of' => array()); // List of groups, membership in which is required for user to be authenticated. // Configuration example for Windows domains with Active Directory: // define('AUTH_MODULE', 'ldap'); // $GLOBALS['AUTH_MODULE_PARAMS'] = array( -// 'server' => '127.0.0.1', // Domain controller IP address or name. +// 'server' => '127.0.0.1', // Domain controller IP address or name. For secure LDAP use ldaps://hostname:port here. // 'type' => 'ad', // Type of server. // 'base_dn' => 'DC=example,DC=com', // Base distinguished name in LDAP catalog. // 'default_domain' => 'example.com', // Default domain. +// 'tls_cacertdir' => null, // Path to a directory containing CA certificates for secure ldap. +// 'tls_cacertfile' => null, // CA certificate file name for secure ldap. // 'member_of' => array()); // List of groups, membership in which is required for user to be authenticated. + // Leave it empty if membership is not necessary. Otherwise list CN parts only. + // For example: + // array('Ldap Testers') means that the user must be a member Ldap Testers group. + // array('Ldap Testers', 'Ldap Users') means the user must be a member of both Ldap Testers and Ldap Users groups. + +// define('DEBUG', false); // Note: enabling DEBUG breaks redirects as debug output is printed before setting redirect header. Do not enable on production systems. -// define('AUTH_DEBUG', false); // Note: enabling AUTH_DEBUG breaks redirects as debug output is printed before setting redirect header. Do not enable on production systems. +// HTTP_TARGET - defines http target for cross site request forgery protection. +// It can be used when you access the application via a proxy. +// define('HTTP_TARGET', 'localhost:8080'); -// Team managers can set monthly work hour quota for years between the following values. + +// Group managers can set monthly work hour quota for years between the following values. // define('MONTHLY_QUOTA_YEAR_START', 2010); // If nothing is specified, it falls back to 2015. // define('MONTHLY_QUOTA_YEAR_END', 2025); // If nothing is specified, it falls back to 2030. -// Height in pixels for the note input field in time.php. Defaults to 40. -// define('NOTE_INPUT_HEIGHT', 100); + +// A comma-separated list of default plugins for new group registrations. +// Example below enables charts and attachments. +// define('DEFAULT_PLUGINS', 'ch,at'); diff --git a/WEB-INF/lib/Auth.class.php b/WEB-INF/lib/Auth.class.php index 2c3c657da..783721090 100644 --- a/WEB-INF/lib/Auth.class.php +++ b/WEB-INF/lib/Auth.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ class Auth { @@ -32,11 +32,12 @@ class Auth { function isAuthenticated() { if (isset($_SESSION['authenticated'])) { // This check does not work properly because we are not getting here. Need to improve. -// if (!isset($_COOKIE['tt_login'])) { +// if (!isset($_COOKIE[LOGIN_COOKIE_NAME])) { // die ("Your browser's cookie functionality is turned off. Please turn it on."); // } - $GLOBALS['SMARTY']->assign('authenticated', true); // Used in header.tpl for menu display. + global $smarty; + $smarty->assign('authenticated', true); // Used in header.tpl for menu display. return true; } session_write_close(); @@ -63,7 +64,7 @@ function isPasswordExternal() function doLogin($login, $password) { $auth = $this->authenticate($login, $password); - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { + if (isTrue('DEBUG')) { echo '
'; var_dump($auth); echo '
'; } @@ -76,13 +77,13 @@ function doLogin($login, $password) { $sql = "SELECT id FROM tt_users WHERE login = ".$mdb2->quote($login)." AND status = 1"; $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) { - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) + if (isTrue('DEBUG')) echo 'db error!
'; return false; } $val = $res->fetchRow(); if (!$val['id']) { - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) + if (isTrue('DEBUG')) echo 'login "'.$login.'" does not exist in Time Tracker database.
'; return false; } diff --git a/WEB-INF/lib/DateAndTime.class.php b/WEB-INF/lib/DateAndTime.class.php deleted file mode 100644 index d960f008e..000000000 --- a/WEB-INF/lib/DateAndTime.class.php +++ /dev/null @@ -1,364 +0,0 @@ -$sDate parsed, or false on error. - */ - -function my_strptime($sDate, $sFormat) -{ - $aResult = array - ( - 'tm_sec' => 0, - 'tm_min' => 0, - 'tm_hour' => 0, - 'tm_mday' => 1, - 'tm_mon' => 0, - 'tm_year' => 0, - 'tm_wday' => 0, - 'tm_yday' => 0, - 'unparsed' => $sDate, - ); - - while($sFormat != "") - { - // ===== Search a %x element, Check the static string before the %x ===== - $nIdxFound = strpos($sFormat, '%'); - if($nIdxFound === false) - { - - // There is no more format. Check the last static string. - $aResult['unparsed'] = ($sFormat == $sDate) ? "" : $sDate; - break; - } - - $sFormatBefore = mb_substr($sFormat, 0, $nIdxFound); - $sDateBefore = mb_substr($sDate, 0, $nIdxFound); - - if($sFormatBefore != $sDateBefore) break; - - // ===== Read the value of the %x found ===== - $sFormat = mb_substr($sFormat, $nIdxFound); - $sDate = mb_substr($sDate, $nIdxFound); - - $aResult['unparsed'] = $sDate; - - $sFormatCurrent = mb_substr($sFormat, 0, 2); - $sFormatAfter = mb_substr($sFormat, 2); - - $nValue = -1; - $sDateAfter = ""; - switch($sFormatCurrent) - { - case '%S': // Seconds after the minute (0-59) - - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 0) || ($nValue > 59)) return false; - - $aResult['tm_sec'] = $nValue; - break; - - // ---------- - case '%M': // Minutes after the hour (0-59) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 0) || ($nValue > 59)) return false; - - $aResult['tm_min'] = $nValue; - break; - - // ---------- - case '%H': // Hour since midnight (0-23) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 0) || ($nValue > 23)) return false; - - $aResult['tm_hour'] = $nValue; - break; - - // ---------- - case '%d': // Day of the month (1-31) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 1) || ($nValue > 31)) return false; - - $aResult['tm_mday'] = $nValue; - break; - - // ---------- - case '%m': // Months since January (0-11) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 1) || ($nValue > 12)) return false; - - $aResult['tm_mon'] = ($nValue - 1); - break; - - // ---------- - case '%Y': // Years since 1900 - sscanf($sDate, "%4d%[^\\n]", $nValue, $sDateAfter); - - if($nValue < 1900) return false; - - $aResult['tm_year'] = ($nValue - 1900); - break; - - // ---------- - default: - //sscanf($sDate, "%s%[^\\n]", $skip, $sDateAfter); - preg_match('/^(.+)(\s|$)/uU', $sDate, $matches); - if (isset($matches[1])) { - $sDateAfter = mb_substr($sDate, mb_strlen($matches[1])); - } else { - $sDateAfter = ''; - } - //break 2; // Break Switch and while - break; - } - - // ===== Next please ===== - $sFormat = $sFormatAfter; - $sDate = $sDateAfter; - - $aResult['unparsed'] = $sDate; - - } // END while($sFormat != "") - - - // ===== Create the other value of the result array ===== - $nParsedDateTimestamp = mktime($aResult['tm_hour'], $aResult['tm_min'], $aResult['tm_sec'], - $aResult['tm_mon'] + 1, $aResult['tm_mday'], $aResult['tm_year'] + 1900); - - // Before PHP 5.1 return -1 when error - if(($nParsedDateTimestamp === false) - ||($nParsedDateTimestamp === -1)) return false; - - $aResult['tm_wday'] = (int) strftime("%w", $nParsedDateTimestamp); // Days since Sunday (0-6) - $aResult['tm_yday'] = (strftime("%j", $nParsedDateTimestamp) - 1); // Days since January 1 (0-365) - - return $aResult; -} // END of function - -class DateAndTime { - var $mHour = 0; - var $mMinute = 0; - var $mSecond = 0; - var $mMonth; - var $mDay; // day of week - var $mDate; // day of month - var $mYear; - var $mIntrFormat = "%d.%m.%Y %H:%M:%S"; //29.02.2004 16:21:42 internal format date - var $mLocalFormat; - var $mParseResult = 0; - var $mAutoComplete = true; - - /** - * Constructor - * - * @param String $format - * @param String $strfDateTime - * @return DateAndTime - */ - function __construct($format="",$strfDateTime="") { - $this->mLocalFormat = ($format ? $format : $this->mIntrFormat); - $d = ($strfDateTime ? $strfDateTime : $this->do_strftime($this->mLocalFormat)); - $this->parseVal($d); - } - - function setFormat($format) { - $this->mLocalFormat = $format; - } - - function getFormat() { - return $this->mLocalFormat; - } - - //01 to 31 - function getDate() { return $this->mDate; } - - //0 (for Sunday) through 6 (for Saturday) - function getDay() { return $this->mDay; } - - //01 through 12 - function getMonth() { return $this->mMonth; } - - //1999 or 2003 - function getYear() { return $this->mYear; } - - function setDate($value) { $this->mDate = $value; } - function setMonth($value) { $this->mMonth = $value; } - function setYear($value) { $this->mYear = $value; } - - function setTimestamp($ts) { - $this->mDate = date("d",$ts); - $this->mDay = date("w",$ts); - $this->mMonth = date("m",$ts); - $this->mYear = date("Y",$ts); - $this->mHour = date("H",$ts); - $this->mMinute = date("i",$ts); - $this->mSecond = date("s",$ts); - } - - /** - * Return UNIX timestamp - */ - function getTimestamp() { - return @mktime($this->mHour, $this->mMinute, $this->mSecond, $this->mMonth, $this->mDate, $this->mYear); - } - - function compare($datetime) { - $ts1 = $this->getTimestamp(); - $ts2 = $datetime->getTimestamp(); - if ($ts1<$ts2) return -1; - if ($ts1==$ts2) return 0; - if ($ts1>$ts2) return 1; - } - - function toString($format="") { - if ($this->mParseResult==0) { - if ($format) { - return $this->do_strftime($format, $this->getTimestamp()); - } else { - return $this->do_strftime($this->mLocalFormat, $this->getTimestamp()); - } - } else { - if ($format) { - return $this->do_strftime($format); - } else { - return $this->do_strftime($this->mLocalFormat); - } - } - } - - function parseVal($szDate, $format="") { - $useformat = ($format ? $format : $this->mLocalFormat); - $res = my_strptime($szDate, $useformat); - if ($res !== false) { - $this->mDate = $res['tm_mday']; - $this->mDay = $res['tm_wday']; - $this->mMonth = $res['tm_mon'] + 1; // tm_mon - Months since January (0-11) - $this->mYear = 1900 + $res['tm_year']; // tm_year - Years since 1900 - $this->mHour = $res['tm_hour']; - $this->mMinute = $res['tm_min']; - $this->mSecond = $res['tm_sec']; - $this->mParseResult = 0; - } elseif ($this->mAutoComplete) { - $this->setTimestamp(time()); - $this->mParseResult = 1; - } - } - - function isError() { - if ($this->mParseResult!=0) return true; - return false; - } - - function getClone() { - if (version_compare(phpversion(), '5.0') < 0) { - $d = new DateAndTime($this->getFormat()); - $d->setTimestamp($this->getTimestamp()); - return $d; - } else { - return clone($this); - } - } - - function before(/*DateAndTime*/ $obj) { - if ($this->getTimestamp()<$obj->getTimestamp()) return true; - return false; - } - - function after(/*DateAndTime*/ $obj) { - if ($this->getTimestamp()>$obj->getTimestamp()) return true; - return false; - } - - function equals(/*DateAndTime*/ $obj) { - if ($this->getTimestamp() == $obj->getTimestamp()) return true; - return false; - } - - function nextDate() { - $d = $this->getClone(); - $d->incDay(); - return $d; - } - - function decDay(/*int*/$days=1) { - $this->setTimestamp(@mktime($this->mHour, $this->mMinute, $this->mSecond, $this->mMonth, $this->mDate - $days, $this->mYear)); - } - - function incDay(/*int*/$days=1) { - $this->setTimestamp(@mktime($this->mHour, $this->mMinute, $this->mSecond, $this->mMonth, $this->mDate + $days, $this->mYear)); - } - - /** - * @param $format string Datetime format string - * @return string Preprocessed string with all locale-depended format - * characters replaced by localized i18n strings. - */ - function preprocessFormatString($format) { - global $i18n; - if (isset($GLOBALS['i18n'])) { - // replace locale-dependent strings - $format = str_replace('%a', mb_substr($i18n->getWeekDayName($this->mDay), 0, 3, 'utf-8'), $format); - $format = str_replace('%A', $i18n->getWeekDayName($this->mDay), $format); - $abbrev_month = mb_substr($i18n->monthNames[$this->mMonth], 0, 3, 'utf-8'); - $format = str_replace('%b', $abbrev_month, $format); - $format = str_replace('%h', $abbrev_month, $format); - $format = str_replace('%z', date('O'), $format); - $format = str_replace('%Z', date('O'), $format); // format as 'O' for consistency with JS strftime - if (strpos($format, '%c') !== false) { - $format = str_replace('%c', $this->preprocessFormatString('%a %d %b %Y %T %Z'), $format); - } - } - return $format; - } - - function do_strftime($format, $timestamp = null) - { - if (!is_null($timestamp)) { - return strftime($this->preprocessFormatString($format), $timestamp); - } else { - return strftime($this->preprocessFormatString($format)); - } - } -} diff --git a/WEB-INF/lib/I18n.class.php b/WEB-INF/lib/I18n.class.php index a9f1b4349..bd4e9ffd3 100644 --- a/WEB-INF/lib/I18n.class.php +++ b/WEB-INF/lib/I18n.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ class I18n { @@ -32,47 +32,44 @@ class I18n { var $monthNames; var $weekdayNames; var $weekdayShortNames; - var $holidays; var $keys = array(); // These are our localized strings. - // The getKey obtains localized keyword value. - function getKey($kword) { + // get - obtains a localized value from $keys array. + function get($key) { $value = ''; - $pos = strpos($kword, '.'); // Keywords can have separating dots such as in form.login.about. + $pos = strpos($key, '.'); // Keywords can have separating dots such as in form.login.about. if (!($pos === false)) { - $words = explode('.', $kword); + $words = explode('.', $key); $str = ''; foreach ($words as $word) { $str .= "['".$word."']"; } eval("\$value = \$this->keys".$str.";"); } else { - $value = $this->keys[$kword]; + $value = $this->keys[$key]; } return $value; } - // TODO: refactoring ongoing down from here... - function getWeekDayName($id) { - $id = intval($id); - return $this->weekdayNames[$id]; - } - - function load($localName) { - $kw = array(); - $filename = strtolower($localName) . '.lang.php'; - $inc_filename = RESOURCE_DIR . '/' . $this->defaultLang . '.lang.php'; + // get - keyExists determines if a key exists. + function keyExists($key) { + $value = $this->get($key); + return ($value !== null); + } - if (file_exists($inc_filename)) { - include($inc_filename); + // load - loads localized strings into $keys array by first going through the default file (en.lang.php) + // and then through the requested language file (which is supplied as parameter), + // (this means we end up with default English strings when keys are missing in the translation file), + // and then from a group custom translation field, if available. + function load($langName) { + // Load default English keys first. + $defaultFileName = RESOURCE_DIR . '/' . $this->defaultLang . '.lang.php'; + if (file_exists($defaultFileName)) { + include($defaultFileName); $this->monthNames = $i18n_months; $this->weekdayNames = $i18n_weekdays; - - $this->weekdayShortNames = $i18n_weekdays_short; - if (defined('SHOW_HOLIDAYS') && isTrue(SHOW_HOLIDAYS)) { - $this->holidays = $i18n_holidays; - } + $this->weekdayShortNames = $i18n_weekdays_short; foreach ($i18n_key_words as $kword=>$value) { $pos = strpos($kword, "."); @@ -89,17 +86,17 @@ function load($localName) { } } - $inc_filename = RESOURCE_DIR . '/' . $filename; - if (file_exists($inc_filename) && ($localName != $this->defaultLang)) { - require($inc_filename); + // Now load the keys from the requested file. + // This overwrites already loaded English strings. + $requestedFileName = strtolower($langName) . '.lang.php'; + $requestedFileName = RESOURCE_DIR . '/' . $requestedFileName; + if (file_exists($requestedFileName) && ($langName != $this->defaultLang)) { + require($requestedFileName); - $this->lang = $localName; + $this->lang = $langName; $this->monthNames = $i18n_months; $this->weekdayNames = $i18n_weekdays; - $this->weekdayShortNames = $i18n_weekdays_short; - if (defined('SHOW_HOLIDAYS') && isTrue(SHOW_HOLIDAYS)) { - $this->holidays = $i18n_holidays; - } + $this->weekdayShortNames = $i18n_weekdays_short; foreach ($i18n_key_words as $kword=>$value) { if (!$value) continue; $pos = strpos($kword, "."); @@ -114,10 +111,40 @@ function load($localName) { $this->keys[$kword] = $value; } } - return true; + } + + // Now load custom translation for group. + global $user; + $customTranslation = $user->getCustomTranslation(); + if ($customTranslation != null) { + $lines = preg_split("/\r\n|\n|\r/", $customTranslation); + for ($i = 0; $i < count($lines); $i++) { + $parts = explode('=', $lines[$i]); + if (count($parts) != 2) continue; + + $key = trim($parts[0]); + $value = trim($parts[1]); + // Escape single quotes and backslashes. + $value = addcslashes($value, "'\\"); + $value = htmlspecialchars($value); + + $pos = strpos($key, "."); + if (!($pos === false)) { + $p = explode(".", $key); + $str = ""; + foreach ($p as $w) { + $str .= "[\"".$w."\"]"; + } + eval("\$this->keys".$str."='".$value."';"); + } else { + $this->keys[$key] = $value; + } + } } } + // hasLang determines if a file for requested language exists. + // This is a helper function for getBrowserLanguage below. function hasLang($lang) { $filename = RESOURCE_DIR . '/' . strtolower($lang) . '.lang.php'; @@ -151,7 +178,7 @@ function getBrowserLanguage() return false; } - // getLangFileList() returns a list of language files. + // getLangFileList() returns a list of available language files. static function getLangFileList() { $fileList = array(); $d = @opendir(RESOURCE_DIR); @@ -166,8 +193,15 @@ static function getLangFileList() { return $fileList; } + // getLangFromFilename returns language designation from a file name such as (ru, pt-br, etc.). static function getLangFromFilename($filename) { return substr($filename, 0, strpos($filename, '.')); } + + // getWeekDayName returns a localized weekday name. + function getWeekDayName($id) { + $id = (int) $id; + return $this->weekdayNames[$id]; + } } diff --git a/WEB-INF/lib/Period.class.php b/WEB-INF/lib/Period.class.php deleted file mode 100644 index c14f17d53..000000000 --- a/WEB-INF/lib/Period.class.php +++ /dev/null @@ -1,147 +0,0 @@ -week_start; - - $date_begin = new DateAndTime(); - $date_begin->setFormat($date_point->getFormat()); - $date_end = new DateAndTime(); - $date_end->setFormat($date_point->getFormat()); - $t_arr = localtime($date_point->getTimestamp()); - $t_arr[5] = $t_arr[5] + 1900; - - if ($t_arr[6] < $startWeek) { - $startWeekBias = $startWeek - 7; - } else { - $startWeekBias = $startWeek; - } - - switch ($period_name) { - case INTERVAL_THIS_DAY: - $date_begin->setTimestamp($date_point->getTimestamp()); - $date_end->setTimestamp($date_point->getTimestamp()); - break; - case INTERVAL_THIS_WEEK: - $date_begin->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+$startWeekBias,$t_arr[5])); - $date_end->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+6+$startWeekBias,$t_arr[5])); - break; - case INTERVAL_LAST_WEEK: - $date_begin->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]-7+$startWeekBias,$t_arr[5])); - $date_end->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]-1+$startWeekBias,$t_arr[5])); - break; - case INTERVAL_THIS_MONTH: - $date_begin->setTimestamp(mktime(0,0,0,$t_arr[4]+1,1,$t_arr[5])); - $date_end->setTimestamp(mktime(0,0,0,$t_arr[4]+2,0,$t_arr[5])); - break; - case INTERVAL_LAST_MONTH: - $date_begin->setTimestamp(mktime(0,0,0,$t_arr[4],1,$t_arr[5])); - $date_end->setTimestamp(mktime(0,0,0,$t_arr[4]+1,0,$t_arr[5])); - break; - - case INTERVAL_THIS_YEAR: - $date_begin->setTimestamp(mktime(0, 0, 0, 1, 1, $t_arr[5])); - $date_end->setTimestamp(mktime(0, 0, 0, 12, 31, $t_arr[5])); - break; - } - $this->mBeginDate = &$date_begin; - $this->mEndDate = &$date_end; - } - - /** - * Return all days by period - * - * @return array - */ - function getAllDays() { - $ret_array = array(); - if ($this->mBeginDate->before($this->mEndDate)) { - $d = $this->getBegin(); - while ($d->before($this->getEnd())) { - array_push($ret_array, $d); - $d = $d->nextDate(); - } - array_push($ret_array, $d); - } else { - array_push($ret_array, $this->mBeginDate); - } - return $ret_array; - } - - function setPeriod($b_date, $e_date) { - $this->mBeginDate = $b_date; - $this->mEndDate = $e_date; - } - - // return date object - function getBegin() { - return $this->mBeginDate; - } - - // return date object - function getEnd() { - return $this->mEndDate; - } - - // return date string - function getBeginDate($format="") { - return $this->mBeginDate->toString($format); - } - - // return date string - function getEndDate($format="") { - return $this->mEndDate->toString($format); - } - - function getArray($format="") { - $result = array(); - $d = $this->getBegin(); - while ($d->before($this->getEnd())) { - $result[] = $d->toString($format); - $d = $d->nextDate(); - } - return $result; - } -} diff --git a/WEB-INF/lib/auth/Auth_db.class.php b/WEB-INF/lib/auth/Auth_db.class.php index 40743b4e3..b5fbdaef9 100644 --- a/WEB-INF/lib/auth/Auth_db.class.php +++ b/WEB-INF/lib/auth/Auth_db.class.php @@ -1,30 +1,6 @@ quote($login)." AND password = md5(".$mdb2->quote($password).") AND status = 1"; + $sql = "SELECT id FROM tt_users". + " WHERE login = ".$mdb2->quote($login)." AND password = md5(".$mdb2->quote($password).") AND status = 1"; $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) { @@ -53,42 +29,46 @@ function authenticate($login, $password) } $val = $res->fetchRow(); - if ($val['id'] > 0) { + if (isset($val['id']) && $val['id'] > 0) { return array('login'=>$login,'id'=>$val['id']); } else { - // If the OLD_PASSWORDS option is defined - set it. - if (defined('OLD_PASSWORDS') && isTrue(OLD_PASSWORDS)) { + if (isTrue('OLD_PASSWORDS')) { $sql = "SET SESSION old_passwords = 1"; $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) { die($res->getMessage()); - } + } } - // Try legacy password match. This is needed for compatibility with older versions of TT. $sql = "SELECT id FROM tt_users - WHERE login = ".$mdb2->quote($login)." AND password = password(".$mdb2->quote($password).") AND status = 1"; + WHERE login = ".$mdb2->quote($login)." AND password = old_password(".$mdb2->quote($password).") AND status = 1"; $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) { - die($res->getMessage()); + return false; // Simply return false for a meaningful error message on screen, see the comment below. + // die($res->getMessage()); // old_password() function is removed in MySQL 5.7.5. + // We are getting a confusing "MDB2 Error: not found" in this case if we die. + // TODO: perhaps it's time to simplify things and remove handling of old passwords completely. + // HOWEVER: some users apparently never change their passwords. When I tried removing OLD_PASSWORDS + // support in November 2018, there were login issues with such users. } $val = $res->fetchRow(); - if ($val['id'] > 0) { + if (isset($val['id']) && $val['id'] > 0) { return array('login'=>$login,'id'=>$val['id']); } } // Special handling for admin@localhost - search for an account with admin role with a matching password. if ($login == 'admin@localhost') { - $sql = "SELECT id, login FROM tt_users - WHERE role = 1024 AND password = md5(".$mdb2->quote($password).") AND status = 1"; + $sql = "SELECT u.id, u.login FROM tt_users u". + " LEFT JOIN tt_roles r on (u.role_id = r.id)". + " WHERE r.rank = 1024 AND password = md5(".$mdb2->quote($password).") AND u.status = 1"; $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) { die($res->getMessage()); } $val = $res->fetchRow(); - if ($val['id'] > 0) { + if (isset($val['id']) && $val['id'] > 0) { return array('login'=>$val['login'],'id'=>$val['id']); } } diff --git a/WEB-INF/lib/auth/Auth_ldap.class.php b/WEB-INF/lib/auth/Auth_ldap.class.php index 37101678e..b783276cf 100644 --- a/WEB-INF/lib/auth/Auth_ldap.class.php +++ b/WEB-INF/lib/auth/Auth_ldap.class.php @@ -1,30 +1,6 @@ array()); is used in config.php. -// Note 2: search is likely to not work properly with OpenLDAP as well because of Windows specific filtering code in there -// (we are looking for matches for Windows-specific samaccountname property). Search needs to be redone during the next -// refactoring effort. /** @@ -53,10 +25,9 @@ class Auth_ldap extends Auth { function __construct($params) { + global $smarty; $this->params = $params; - if (isset($GLOBALS['smarty'])) { - $GLOBALS['smarty']->assign('Auth_ldap_params', $this->params); - } + $smarty->assign('Auth_ldap_params', $this->params); } function ldap_escape($str){ @@ -65,7 +36,7 @@ function ldap_escape($str){ foreach ($illegal as $id => $char) { $legal[$id] = "\\".$char; } - $str = str_replace($illegal, $legal,$str); //replace them + $str = str_replace($illegal, $legal, $str); //replace them return $str; } @@ -97,7 +68,7 @@ function authenticate($login, $password) $lc = ldap_connect($this->params['server']); - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { + if (isTrue('DEBUG')) { echo '
'; echo '$lc='; var_dump($lc); echo '
'; echo 'ldap_error()='; echo ldap_error($lc); echo '
'; @@ -107,27 +78,36 @@ function authenticate($login, $password) ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option($lc, LDAP_OPT_REFERRALS, 0); - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { + if (isTrue('DEBUG')) { ldap_set_option($lc, LDAP_OPT_DEBUG_LEVEL, 7); } + // Additional options for secure ldap. + // This insert is based on https://www.anuko.com/forum/viewtopic.php?f=4&t=2091 + // I can't test it at the moment. If things break please let us know! + if (isset($this->params['tls_cacertdir'])) { + ldap_set_option(null, LDAP_OPT_X_TLS_CACERTDIR, $this->params['tls_cacertdir']); + } + if (isset($this->params['tls_cacertfile'])) { + ldap_set_option(null, LDAP_OPT_X_TLS_CACERTFILE, $this->params['tls_cacertfile']); + } + // End of addiitional options for secure ldap. // We need to handle Windows AD and OpenLDAP differently. - if ($this->params['type'] != 'openldap') { + if ($this->params['type'] == 'ad') { - // check if the user specified full login + // Check if user specified full login. if (strpos($login, '@') === false) { - // append default domain + // Append default domain. $login .= '@' . $this->params['default_domain']; } - - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { + if (isTrue('DEBUG')) { echo '$login='; var_dump($login); echo '
'; } $lb = @ldap_bind($lc, $login, $password); - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { + if (isTrue('DEBUG')) { echo '$lb='; var_dump($lb); echo '
'; echo 'ldap_error()='; echo ldap_error($lc); echo '
'; } @@ -138,18 +118,17 @@ function authenticate($login, $password) } if ($member_of) { - // get groups + // Get groups the user is a member of from AD LDAP server. - $filter = 'samaccountname='.Auth_ldap::ldap_escape($login); - $fields = array('samaccountname', 'mail', 'memberof', 'department', 'displayname', 'telephonenumber', 'primarygroupid'); + $filter = 'userPrincipalName='.Auth_ldap::ldap_escape($login); + $fields = array('memberof'); $sr = @ldap_search($lc, $this->params['base_dn'], $filter, $fields); - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { + if (isTrue('DEBUG')) { echo '$sr='; var_dump($sr); echo '
'; echo 'ldap_error()='; echo ldap_error($lc); echo '
'; } - // if search failed it's likely that account is disabled if (!$sr) { ldap_unbind($lc); return false; @@ -157,7 +136,7 @@ function authenticate($login, $password) $entries = @ldap_get_entries($lc, $sr); - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { + if (isTrue('DEBUG')) { echo '$entries='; var_dump($entries); echo '
'; echo 'ldap_error()='; echo ldap_error($lc); echo '
'; } @@ -169,20 +148,19 @@ function authenticate($login, $password) $groups = array(); - // extract group names from - // assuming the groups are in format: CN=,... + // Extract group names. Assume the groups are in format: CN=,... for ($i = 0; $i < @$entries[0]['memberof']['count']; $i++) { $grp = $entries[0]['memberof'][$i]; $grp_fields = explode(',', $grp); $groups[] = substr($grp_fields[0], 3); } - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { + if (isTrue('DEBUG')) { echo '$member_of'; var_dump($member_of); echo '
'; }; - // check for group membership - foreach ($member_of as $check_grp) { + // Check for group membership. + foreach ($member_of as $check_grp) { if (!in_array($check_grp, $groups)) { ldap_unbind($lc); return false; @@ -191,26 +169,28 @@ function authenticate($login, $password) } ldap_unbind($lc); - return array('login' => $login, 'data' => $entries, 'member_of' => $groups); - } else { + } + + if ($this->params['type'] == 'openldap') { // Assuming OpenLDAP server. - $login_oldap = 'uid='.$login.','.$this->params['base_dn']; - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { - echo '$login_oldap='; var_dump($login_oldap); echo '
'; + if (empty($this->params['user_login_attribute'])) { + $user_login_attribute = 'uid'; + } else { + $user_login_attribute = $this->params['user_login_attribute']; } - // check if the user specified full login - if (strpos($login, '@') === false) { - // append default domain - $login .= '@' . $this->params['default_domain']; + $login_oldap = $user_login_attribute.'='.Auth_ldap::ldap_escape($login).','.$this->params['base_dn']; + + if (isTrue('DEBUG')) { + echo '$login_oldap='; var_dump($login_oldap); echo '
'; } $lb = @ldap_bind($lc, $login_oldap, $password); - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { + if (isTrue('DEBUG')) { echo '$lb='; var_dump($lb); echo '
'; echo 'ldap_error()='; echo ldap_error($lc); echo '
'; } @@ -221,13 +201,18 @@ function authenticate($login, $password) } if ($member_of) { - // get groups - $filter = 'samaccountname='.Auth_ldap::ldap_escape($login_oldap); - $fields = array('samaccountname', 'mail', 'memberof', 'department', 'displayname', 'telephonenumber', 'primarygroupid'); + if (isTrue('DEBUG')) { + echo '$member_of : '; var_dump($member_of); echo '
'; + } + + $filter = $user_login_attribute.'='.Auth_ldap::ldap_escape($login); // ldap search filter + $fields = array('memberof'); // ldap search attributes $sr = @ldap_search($lc, $this->params['base_dn'], $filter, $fields); - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { + if (isTrue('DEBUG')) { + echo '$filter='; var_dump($filter); echo '
'; + echo '$fields='; var_dump($fields); echo '
'; echo '$sr='; var_dump($sr); echo '
'; echo 'ldap_error()='; echo ldap_error($lc); echo '
'; } @@ -240,7 +225,7 @@ function authenticate($login, $password) $entries = @ldap_get_entries($lc, $sr); - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { + if (isTrue('DEBUG')) { echo '$entries='; var_dump($entries); echo '
'; echo 'ldap_error()='; echo ldap_error($lc); echo '
'; } @@ -250,24 +235,26 @@ function authenticate($login, $password) return false; } - $groups = array(); + $groups = array(); // existing ldap group memberships - // extract group names from - // assuming the groups are in format: CN=,... for ($i = 0; $i < @$entries[0]['memberof']['count']; $i++) { - $grp = $entries[0]['memberof'][$i]; - $grp_fields = explode(',', $grp); - $groups[] = substr($grp_fields[0], 3); - } - - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { - echo '$member_of'; var_dump($member_of); echo '
'; + $grp = $entries[0]['memberof'][$i]; + $groups[] = $grp; // append group to array + if (isTrue('DEBUG')) { + var_dump($grp); echo ' appended to $groups
'; + } } // check for group membership foreach ($member_of as $check_grp) { + if (isTrue('DEBUG')) { + echo '$check_grp:'; var_dump($check_grp); echo '
'; + } if (!in_array($check_grp, $groups)) { ldap_unbind($lc); + if (isTrue('DEBUG')) { + echo '=> '.$login.' is not a member of '.$check_grp.'
'; + } return false; } } @@ -275,8 +262,17 @@ function authenticate($login, $password) ldap_unbind($lc); + // check if the user specified full login + if (strpos($login, '@') === false) { + // append default domain + $login .= '@' . $this->params['default_domain']; + } + return array('login' => $login, 'data' => $entries, 'member_of' => $groups); } + + // Server type is neither 'ad' or 'openldap'. + return false; } function isPasswordExternal() { diff --git a/WEB-INF/lib/common.lib.php b/WEB-INF/lib/common.lib.php index 81c870f8c..79aba106b 100644 --- a/WEB-INF/lib/common.lib.php +++ b/WEB-INF/lib/common.lib.php @@ -1,30 +1,6 @@ ') || stristr($val, '\n"; - - return $str; + $html .= "\n"; } - function toStringControl() { - return $this->toString(); - } + // Finished printing calendar table. - function _getWeekDayBefore($year, $month) { - $weekday = date ( "w", mktime ( 2, 0, 0, $month, 1 - $this->weekStartDay, $year ) ); - return array( - mktime ( 0, 0, 0, $month, 1 - $weekday, $year ), - mktime ( 0, 0, 0, $month, 1, $year ), - mktime ( 0, 0, 0, $month + 1, 0, $year ), - (1 - $weekday) - ); - } + // Print Today link. + $html .= " name."=".ttDate::dateFromUnixTimestamp()."\" tabindex=\"-1\">".$i18n->get('label.today')."\n"; + $html .= "\n"; - function _genStyles() { - $str = "\n"; - return $str; - } - - // _getActiveDates returns an array of dates, for which entries exist for user. - // Type of entries (time or expenses) is determined by $this->highlight value. - function _getActiveDates($start, $end) { + // Add a hidden control for selected date. + $html .= "name\" value=\"$selectedDate\">\n"; + $html .= "\n\n"; + return $html; + } + + // getActiveDates returns an array of dates, for which entries exist for user. + // Type of entries (time or expenses) is determined by $this->highlight value. + function getActiveDates($start, $end) { - global $user; - $user_id = $user->getActiveUser(); + global $user; + $user_id = $user->getUser(); - $table = ($this->highlight == 'expenses') ? 'tt_expense_items' : 'tt_log'; + $table = ($this->highlight == 'expenses') ? 'tt_expense_items' : 'tt_log'; - $mdb2 = getConnection(); - - $start_date = date("Y-m-d", $start); - $end_date = date("Y-m-d", $end); - $sql = "SELECT date FROM $table WHERE date >= '$start_date' AND date <= '$end_date' AND user_id = $user_id AND status = 1"; - $res = $mdb2->query($sql); - if (!is_a($res, 'PEAR_Error')) { - while ($row = $res->fetchRow()) { - $out[] = date('Y-m-d', strtotime($row['date'])); - } - return @$out; + $mdb2 = getConnection(); + + $start_date = date("Y-m-d", $start); + $end_date = date("Y-m-d", $end); + $sql = "SELECT date FROM $table WHERE date >= '$start_date' AND date <= '$end_date' AND user_id = $user_id AND status = 1"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + while ($row = $res->fetchRow()) { + $out[] = date('Y-m-d', strtotime($row['date'])); } - else - return false; + return @$out; } + else + return false; + } } diff --git a/WEB-INF/lib/form/Checkbox.class.php b/WEB-INF/lib/form/Checkbox.class.php index 5ba791651..619d39fb3 100644 --- a/WEB-INF/lib/form/Checkbox.class.php +++ b/WEB-INF/lib/form/Checkbox.class.php @@ -23,52 +23,41 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('form.FormElement'); class Checkbox extends FormElement { - var $mChecked = false; - var $mOptions = null; - var $cClassName = "Checkbox"; - function __construct($name,$value="") - { - $this->mName = $name; - $this->mValue = $value; - } + function __construct($name) { + $this->class = 'Checkbox'; + $this->name = $name; + } - function setChecked($value) { $this->mChecked = $value; } - function isChecked() { return $this->mChecked; } - - function setData($value) { $this->mOptions = $value; } - function getData() { return $this->mOptions; } - - function toStringControl() { - if (!$this->isRenderable()) return ""; - - if ($this->mId=="") $this->mId = $this->mName; - - $html = "\n\tmName\" id=\"$this->mId\""; - - if ($this->mTabindex!="") - $html .= " tabindex=\"$this->mTabindex\""; - - if ($this->mOnChange!="") - $html .= " onchange=\"$this->mOnChange\""; + function getHtml() { + if ($this->id == '') $this->id = $this->name; + + $html = "\n\tid\" name=\"$this->name\""; + + if ($this->value) + $html.= " checked=\"true\""; + + if ($this->on_change!="") + $html .= " onchange=\"$this->on_change\""; - if ($this->mStyle!="") - $html .= " style=\"$this->mStyle\""; + if ($this->style!="") + $html .= " style=\"$this->style\""; + - if ($this->mChecked || (($this->mValue == $this->mOptions) && ($this->mValue != null))) - $html .= " checked=\"true\""; - if (!$this->isEnable()) + if (!$this->isEnabled()) $html .= " disabled=\"disabled\""; - - $html .= " value=\"".htmlspecialchars($this->mOptions)."\""; + + // Provide a value so that we pass "1" for set checkboxes on form submit. + // Otherwise the default is "on" string, which is not what we want. + $html .= " value=\"1\""; $html .= "/>\n"; diff --git a/WEB-INF/lib/form/CheckboxCellRenderer.class.php b/WEB-INF/lib/form/CheckboxCellRenderer.class.php index 8ebfd6db6..cd9682703 100644 --- a/WEB-INF/lib/form/CheckboxCellRenderer.class.php +++ b/WEB-INF/lib/form/CheckboxCellRenderer.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('form.DefaultCellRenderer'); @@ -33,7 +33,7 @@ class CheckboxCellRenderer extends DefaultCellRenderer { function render(&$table, $value, $row, $column, $selected = false) { $html = 'mWidth!='' ? ' width="'.$this->mWidth.'"' : ''); - $html .= ">getName()."[]\" id=\"".$table->getName()."_".$row."\" type=\"checkbox\" value=\"".$value."\""; + $html .= ">name."[]\" id=\"".$table->name."_".$row."\" type=\"checkbox\" value=\"".$value."\""; if($this->getOnChangeAdd()) { $html .= " onclick=\"".$this->getOnChangeAdd()."\""; } diff --git a/WEB-INF/lib/form/CheckboxGroup.class.php b/WEB-INF/lib/form/CheckboxGroup.class.php index f9d850fc2..68b24853b 100644 --- a/WEB-INF/lib/form/CheckboxGroup.class.php +++ b/WEB-INF/lib/form/CheckboxGroup.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('form.FormElement'); @@ -33,17 +33,15 @@ class CheckboxGroup extends FormElement { var $mOptions = array(); var $mLayout = "V"; var $mGroupIn = 1; - var $cClassName = "CheckboxGroup"; var $mDataKeys = array(); var $mDataDeep = 1; var $lSelAll = "All"; var $lSelNone = "None"; - function __construct($name,$value="") - { - $this->mName = $name; - $this->mValue = $value; - } + function __construct($name) { + $this->class = 'CheckboxGroup'; + $this->name = $name; + } function setChecked($value) { $this->mChecked = $value; } function isChecked() { return $this->mChecked; } @@ -60,16 +58,15 @@ function getLayout() { return $this->mLayout; } function setGroupIn($value) { $this->mGroupIn = $value; if ($this->mGroupIn<1) $this->mGroupIn = 1;} function getGroupIn() { return $this->mGroupIn; } - function setLocalization($i18n) { - FormElement::setLocalization($i18n); - $this->lSelAll = $i18n->getKey('label.select_all'); - $this->lSelNone = $i18n->getKey('label.select_none'); + function localize() { + global $i18n; + $this->lSelAll = $i18n->get('label.select_all'); + $this->lSelNone = $i18n->get('label.select_none'); } - function toStringControl() { - if (!$this->isRenderable()) return ""; - - if ($this->mId=="") $this->mId = $this->mName; + function getHtml() { + + if ($this->id=="") $this->id = $this->name; $renderArray = array(); $renderCols = 0; @@ -86,14 +83,14 @@ function toStringControl() { $optkey = $optval[$this->mDataKeys[0]]; $optval = $optval[$this->mDataKeys[1]]; } - $html = "mName[]\" id=\"$this->mId"."_".$i."\""; - if (is_array($this->mValue)) { - foreach ($this->mValue as $value) { - if (($value == $optkey) && ($value != null)) + $html = "name[]\" id=\"$this->id"."_".$i."\""; + if (is_array($this->value)) { + foreach ($this->value as $element) { + if (($element == $optkey) && ($element != null)) $html .= " checked=\"true\""; } } - $html .= " value=\"".htmlspecialchars($optkey)."\"> "; + $html .= " value=\"".htmlspecialchars($optkey)."\"> "; $renderArray[$col][$row] = $html; $col++; @@ -114,14 +111,14 @@ function toStringControl() { $optkey = $optval[$this->mDataKeys[0]]; $optval = $optval[$this->mDataKeys[1]]; } - $html = "mName[]\" id=\"$this->mId"."_".$i."\""; - if (is_array($this->mValue)) { - foreach ($this->mValue as $value) { - if (($value == $optkey) && ($value != null)) + $html = "name[]\" id=\"$this->id"."_".$i."\""; + if (is_array($this->value)) { + foreach ($this->value as $element) { + if (($element == $optkey) && ($element != null)) $html .= " checked=\"true\""; } } - $html .= " value=\"".htmlspecialchars($optkey)."\"> "; + $html .= " value=\"".htmlspecialchars($optkey)."\"> "; $renderArray[$col][$row] = $html; $row++; @@ -132,8 +129,8 @@ function toStringControl() { } - $html = "\n\tmStyle."\"> +{else} +
\n"; - $html .= ''.$this->lSelAll.' / '.$this->lSelNone.''; + $html = "\n\tstyle."\">\n"; $html .= "mBgColor."\" onmouseover=\"setRowBackground(this, '".$this->mBgColorOver."')\" onmouseout=\"setRowBackground(this, null)\">\n"; - for ($col = 0; $col < $this->getColumnCount(); $col++) { - if (0 == $col && strtolower(get_class($this->mColumns[$col]->getRenderer())) == 'checkboxcellrenderer') { - // Checkbox for the row. Determine if selected. - $selected = false; - if (is_array($this->mValue)) { - foreach ($this->mValue as $p) { - if ($p == $this->mData[$row][$this->mKeyField]) { - $selected = true; - break; + if (is_array($this->mData)) { + for ($row = 0; $row < count($this->mData); $row++) { + $html .= "\nmBgColor."\" onmouseover=\"setRowBackground(this, '".$this->mBgColorOver."')\" onmouseout=\"setRowBackground(this, null)\">\n"; + for ($col = 0; $col < $this->getColumnCount(); $col++) { + if (0 == $col && strtolower(get_class($this->mColumns[$col]->getRenderer())) == 'checkboxcellrenderer') { + // Checkbox for the row. Determine if selected. + $selected = false; + if (is_array($this->value)) { + foreach ($this->value as $p) { + if ($p == $this->mData[$row][$this->mKeyField]) { + $selected = true; + break; + } } } + // Render control checkbox. + $html .= $this->mColumns[$col]->renderCell($this->mData[$row][$this->mKeyField], $row, $col, $selected); + } else { + // Render regular cell. + $html .= $this->mColumns[$col]->renderCell($this->getValueAt($row, $col), $row, $col); + } + } + $html .= "\n"; + } + } + + // Print footers. + if (($this->mInteractive && (count($this->mFooters) > 1)) || (!$this->mInteractive && (count($this->mFooters) > 0))) { + $html .= "mRowOptions) > 0) { + foreach ($this->mRowOptions as $k=>$v) { + $html .= " $k=\"$v\""; + } + } + $html .= ">\n"; + foreach ($this->mFooters as $footer) { + $html .= "mHeaderOptions) > 0) { + foreach ($this->mHeaderOptions as $k=>$v) { + $html .= " $k=\"$v\""; } - // Render control checkbox. - $html .= $this->mColumns[$col]->renderCell($this->mData[$row][$this->mKeyField], $row, $col, $selected); - } else { - // Render regular cell. - $html .= $this->mColumns[$col]->renderCell($this->getValueAt($row, $col), $row, $col); } + $html .= ">$footer\n"; } $html .= "\n"; } @@ -191,7 +216,7 @@ function _addJavaScript() { // setAll - checks / unchecks all checkboxes in the table. $html .= "function setAll(value) {\n"; $html .= "\tfor (var i = 0; i < ".$this->getFormName().".elements.length; i++) {\n"; - $html .= "\t\tif ((".$this->getFormName().".elements[i].type=='checkbox') && (".$this->getFormName().".elements[i].name=='".$this->getName()."[]')) {\n"; + $html .= "\t\tif ((".$this->getFormName().".elements[i].type=='checkbox') && (".$this->getFormName().".elements[i].name=='".$this->name."[]')) {\n"; $html .= "\t\t\t".$this->getFormName().".elements[i].checked=value;\n"; if ($this->getIAScript()) { $html .= "\t\t\t".$this->getIAScript()."(".$this->getFormName().".elements[i]);\n"; diff --git a/WEB-INF/lib/form/TableColumn.class.php b/WEB-INF/lib/form/TableColumn.class.php index 06cad0514..87538eaa8 100644 --- a/WEB-INF/lib/form/TableColumn.class.php +++ b/WEB-INF/lib/form/TableColumn.class.php @@ -23,13 +23,18 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('form.DefaultCellRenderer'); +// TableColumn class represents a single column in a table. class TableColumn { - var $mTitle = ""; + var $colHeader = ''; // Column header. + var $colFooter = ''; // Column footer, example: totals in week view. + +// TODO: refactoring ongoing down from here. + var $mIndexField = ""; var $mRenderer = null; var $mWidth = ""; @@ -37,9 +42,10 @@ class TableColumn { var $mBgColor = "#ffffff"; var $mFgColor = "#000000"; - function __construct($indexField, $title="",$renderer=null) { + function __construct($indexField, $header = '', $renderer = null, $footer = '') { $this->mIndexField = $indexField; - $this->mTitle = $title; + $this->colHeader = $header; + $this->colFooter = $footer; if ($renderer!=null) { $this->mRenderer = $renderer; } else { @@ -47,8 +53,9 @@ function __construct($indexField, $title="",$renderer=null) { } } - function getHeader() { return $this->mTitle; } - + function getHeader() { return $this->colHeader; } + function getFooter() { return $this->colFooter; } + function getField() { return $this->mIndexField; } function setTable(&$table) { $this->mTable = &$table; } diff --git a/WEB-INF/lib/form/TextArea.class.php b/WEB-INF/lib/form/TextArea.class.php index be0c8caa2..4faac1447 100644 --- a/WEB-INF/lib/form/TextArea.class.php +++ b/WEB-INF/lib/form/TextArea.class.php @@ -23,81 +23,42 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('form.FormElement'); class TextArea extends FormElement { - var $mValue; - var $mPassword = false; - var $mColumns = ""; - var $mRows = ""; - var $cClassName = "TextArea"; + var $mOnKeyPress = ""; - function __construct($name,$value="") - { - $this->mName = $name; - $this->mValue = $value; - } - - function setColumns($value) { $this->mColumns = $value; } - function getColumns() { return $this->mColumns; } + function __construct($name) + { + $this->class = 'TextArea'; + $this->name = $name; + } - function setRows($value) { $this->mRows = $value; } - function getRows() { return $this->mRows; } - - function toStringControl() { - if (!$this->isRenderable()) return ""; - - if ($this->mId=="") $this->mId = $this->mName; - - $js_maxlen = ""; + function getHtml() { + + if (empty($this->id)) + $this->id = $this->name; $html = "\n\tmName\" id=\"$this->mId\""; - - if ($this->mColumns!="") - $html .= " cols=\"$this->mColumns\""; - - if ($this->mRows!="") - $html .= " rows=\"$this->mRows\""; - - if ($this->mMaxLength!="") { + $html .= " name=\"$this->name\" id=\"$this->id\""; + + if ($this->max_length!="") { if ($this->mOnKeyPress) $this->mOnKeyPress .= ";"; - $this->mOnKeyPress .= "return validateMaxLenght_".$this->mName."(this, event);"; - $js_maxlen = $this->getExtraScript(); - $html .= " maxlength=\"$this->mMaxLength\""; + $html .= " maxlength=\"$this->max_length\""; } - if ($this->mStyle!="") - $html .= " style=\"$this->mStyle\""; + if ($this->style!="") + $html .= " style=\"$this->style\""; if ($this->mOnKeyPress) { $html .= " onkeypress=\"$this->mOnKeyPress\""; } - + if(!$this->isEnabled()) $html .= " readonly"; $html .= ">".htmlspecialchars($this->getValue()).""; - if ($js_maxlen) $html = $js_maxlen."\n".$html; return $html; } - - function getExtraScript() { - $s = "\n"; - return $s; - } } diff --git a/WEB-INF/lib/form/TextField.class.php b/WEB-INF/lib/form/TextField.class.php index a409410fb..bfc0e551f 100644 --- a/WEB-INF/lib/form/TextField.class.php +++ b/WEB-INF/lib/form/TextField.class.php @@ -23,54 +23,43 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('form.FormElement'); - + class TextField extends FormElement { - var $mValue; - var $mPassword = false; - var $cClassName = "TextField"; - function __construct($name,$value="") - { - $this->mName = $name; - $this->mValue = $value; - } - - function setAsPassword($name) { $this->mPassword = $name; } - function getAsPassword() { return $this->mPassword; } + var $title = null; // Control title (ex: to display a tooltip). + + function __construct($name) + { + $this->class = 'TextField'; + $this->css_class = 'text-field'; + $this->name = $name; + } + + function setTitle($title) { $this->title = $title; } + + function getHtml() { + if (empty($this->id)) $this->id = $this->name; + $html = "\n\tcss_class\""; + $html .= " id=\"$this->id\" name=\"$this->name\""; + if (!empty($this->size)) $html .= " size=\"$this->size\""; + if (!empty($this->style)) $html .= " style=\"$this->style\""; + if (!empty($this->title)) $html .= " title=\"$this->title\""; + if (!empty($this->placeholder)) $html .= " placeholder=\"$this->placeholder\""; + + if($this->isEnabled()) { + if (!empty($this->max_length)) $html .= " maxlength=\"$this->max_length\""; + if (!empty($this->on_change)) $html .= " onchange=\"$this->on_change\""; + } - function toStringControl() { - if (!$this->isRenderable()) return ""; - - if (!$this->isEnable()) { - $html = "mName\" value=\"".htmlspecialchars($this->getValue())."\" readonly>\n"; - } else { - - if ($this->mId=="") $this->mId = $this->mName; - - $html = "\n\tmPassword ? " type=\"password\"" : " type=\"text\""); - $html .= " name=\"$this->mName\" id=\"$this->mId\""; - - if ($this->mSize!="") - $html .= " size=\"$this->mSize\""; - - if ($this->mStyle!="") - $html .= " style=\"$this->mStyle\""; - - if ($this->mMaxLength!="") - $html .= " maxlength=\"$this->mMaxLength\""; - - if ($this->mOnChange!="") - $html .= " onchange=\"$this->mOnChange\""; + $html .= " value=\"".htmlspecialchars($this->getValue())."\""; - $html .= " value=\"".htmlspecialchars($this->getValue())."\""; - $html .= ">"; - } - - return $html; - } + if(!$this->isEnabled()) $html .= " readonly"; + $html .= ">\n"; + return $html; + } } diff --git a/WEB-INF/lib/form/UploadFile.class.php b/WEB-INF/lib/form/UploadFile.class.php index 1055125df..36d0d92a7 100644 --- a/WEB-INF/lib/form/UploadFile.class.php +++ b/WEB-INF/lib/form/UploadFile.class.php @@ -23,46 +23,29 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('form.FormElement'); class UploadFile extends FormElement { - var $mValue; - var $cClassName = "UploadFile"; - var $mMaxSize = 100000; // 100kb - function __construct($name,$value="") - { - $this->mName = $name; - $this->mValue = $value; - } - - function setMaxSize($value) { $this->mMaxSize = $value; } - function getMaxSize() { return $this->mMaxSize; } - - function toStringControl() { - if (!$this->isRenderable()) return ""; - - if ($this->mId=="") $this->mId = $this->mName; - - $html = "\n\tmMaxSize."\"/>"; - $html .= "\n\tmName\" id=\"$this->mId\""; - - $html .= " type=\"file\""; - $html .= ">"; - - // only IE - /*$html = "mName."\" id=\"".$this->mId."\" style=\"display: none;\">\n"; - $html .= "mName."file\">\n"; - $html .= "mName.".click();".$this->mName."file.value=".$this->mName.".value;".$this->mName.".disabled=true;\""; - $html .= " value=\"".$this->getValue()."\">\n"; - $html .= "mMaxSize."\"/>";*/ - - - return $html; - } + var $maxSize = 8388608; // 8MB default max size. + + function __construct($name) + { + $this->class = 'UploadFile'; + $this->name = $name; + } + + function setMaxSize($value) { $this->maxSize = $value; } + + function getHtml() { + + if ($this->id == '') $this->id = $this->name; + + $html = "\n\tmaxSize\">"; + $html .= "\n\tid\" name=\"$this->name\">"; + return $html; + } } diff --git a/WEB-INF/lib/html/HttpRequest.class.php b/WEB-INF/lib/html/HttpRequest.class.php index cd66a8cc6..bc32debcf 100644 --- a/WEB-INF/lib/html/HttpRequest.class.php +++ b/WEB-INF/lib/html/HttpRequest.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ class ttHttpRequest { @@ -42,17 +42,17 @@ function isPost() { return ($this->getMethod() == 'POST'); } - // The getParameter is the primary function of this class. It returns request parameter, + // The getParameter is the primary function of this class. It returns request parameter // identified by $name. - function getParameter($name = "", $default = null) { + function getParameter($name = '', $default = null) { switch ($this->getMethod()) { case 'GET': - if (isset($_GET[$name]) && ($_GET[$name] != "")) + if (isset($_GET[$name]) && ($_GET[$name] != '')) return $_GET[$name]; case 'POST': - if (isset($_POST[$name]) && ($_POST[$name] != "")) + if (isset($_POST[$name]) && ($_POST[$name] != '')) return $_POST[$name]; } return $default; diff --git a/WEB-INF/lib/libchart/classes/libchart.php b/WEB-INF/lib/libchart/classes/libchart.php index f84210fbb..3d1cd5369 100644 --- a/WEB-INF/lib/libchart/classes/libchart.php +++ b/WEB-INF/lib/libchart/classes/libchart.php @@ -1,6 +1,6 @@ x = $x; $this->y = $y; } diff --git a/WEB-INF/lib/libchart/classes/model/XYDataSet.php b/WEB-INF/lib/libchart/classes/model/XYDataSet.php index 2713c2985..619012545 100644 --- a/WEB-INF/lib/libchart/classes/model/XYDataSet.php +++ b/WEB-INF/lib/libchart/classes/model/XYDataSet.php @@ -1,6 +1,6 @@ pointList = array(); } diff --git a/WEB-INF/lib/libchart/classes/model/XYSeriesDataSet.php b/WEB-INF/lib/libchart/classes/model/XYSeriesDataSet.php index 45f961c5f..ff26c600a 100644 --- a/WEB-INF/lib/libchart/classes/model/XYSeriesDataSet.php +++ b/WEB-INF/lib/libchart/classes/model/XYSeriesDataSet.php @@ -1,6 +1,6 @@ titleList = array(); $this->serieList = array(); } diff --git a/WEB-INF/lib/libchart/classes/view/axis/Axis.php b/WEB-INF/lib/libchart/classes/view/axis/Axis.php index 885019639..e0dabb7a3 100644 --- a/WEB-INF/lib/libchart/classes/view/axis/Axis.php +++ b/WEB-INF/lib/libchart/classes/view/axis/Axis.php @@ -1,6 +1,6 @@ min = $min; $this->max = $max; diff --git a/WEB-INF/lib/libchart/classes/view/axis/Bound.php b/WEB-INF/lib/libchart/classes/view/axis/Bound.php index 5497b70c1..f71ee05b4 100644 --- a/WEB-INF/lib/libchart/classes/view/axis/Bound.php +++ b/WEB-INF/lib/libchart/classes/view/axis/Bound.php @@ -1,6 +1,6 @@ labelBoxWidth = 15; $this->labelBoxHeight = 15; } diff --git a/WEB-INF/lib/libchart/classes/view/chart/BarChart.php b/WEB-INF/lib/libchart/classes/view/chart/BarChart.php index c5d9b82a0..1b1ef2f5d 100644 --- a/WEB-INF/lib/libchart/classes/view/chart/BarChart.php +++ b/WEB-INF/lib/libchart/classes/view/chart/BarChart.php @@ -1,6 +1,6 @@ bound = new Bound(); diff --git a/WEB-INF/lib/libchart/classes/view/chart/Chart.php b/WEB-INF/lib/libchart/classes/view/chart/Chart.php index 599b98fec..685b901b3 100644 --- a/WEB-INF/lib/libchart/classes/view/chart/Chart.php +++ b/WEB-INF/lib/libchart/classes/view/chart/Chart.php @@ -1,6 +1,6 @@ plot = new Plot($width, $height); $this->plot->setTitle("Untitled chart"); diff --git a/WEB-INF/lib/libchart/classes/view/chart/HorizontalBarChart.php b/WEB-INF/lib/libchart/classes/view/chart/HorizontalBarChart.php index 5faad4997..5cfc86a2b 100644 --- a/WEB-INF/lib/libchart/classes/view/chart/HorizontalBarChart.php +++ b/WEB-INF/lib/libchart/classes/view/chart/HorizontalBarChart.php @@ -1,6 +1,6 @@ emptyToFullRatio = 1 / 5; $this->plot->setGraphPadding(new Padding(5, 30, 30, 50)); diff --git a/WEB-INF/lib/libchart/classes/view/chart/LineChart.php b/WEB-INF/lib/libchart/classes/view/chart/LineChart.php index 811a060cc..a7642a70c 100644 --- a/WEB-INF/lib/libchart/classes/view/chart/LineChart.php +++ b/WEB-INF/lib/libchart/classes/view/chart/LineChart.php @@ -1,6 +1,6 @@ plot->setGraphPadding(new Padding(5, 30, 50, 50)); } diff --git a/WEB-INF/lib/libchart/classes/view/chart/PieChart.php b/WEB-INF/lib/libchart/classes/view/chart/PieChart.php index 108b5927e..0b5c417a4 100644 --- a/WEB-INF/lib/libchart/classes/view/chart/PieChart.php +++ b/WEB-INF/lib/libchart/classes/view/chart/PieChart.php @@ -1,6 +1,6 @@ plot->setGraphPadding(new Padding(15, 10, 30, 30)); } @@ -64,9 +64,7 @@ protected function computeLayout($hasCaption = true) { * @return integer result of the comparison */ protected function sortPie($v1, $v2) { - return $v1[0] == $v2[0] ? 0 : - $v1[0] > $v2[0] ? -1 : - 1; + return $v1[0] == $v2[0] ? 0 : ($v1[0] > $v2[0] ? -1 : 1); } /** diff --git a/WEB-INF/lib/libchart/classes/view/chart/VerticalBarChart.php b/WEB-INF/lib/libchart/classes/view/chart/VerticalBarChart.php index 65adcf2ad..668047bb9 100644 --- a/WEB-INF/lib/libchart/classes/view/chart/VerticalBarChart.php +++ b/WEB-INF/lib/libchart/classes/view/chart/VerticalBarChart.php @@ -1,6 +1,6 @@ emptyToFullRatio = 1 / 5; $this->plot->setGraphPadding(new Padding(5, 30, 50, 50)); diff --git a/WEB-INF/lib/libchart/classes/view/color/Color.php b/WEB-INF/lib/libchart/classes/view/color/Color.php index d97c7007f..e316c2022 100644 --- a/WEB-INF/lib/libchart/classes/view/color/Color.php +++ b/WEB-INF/lib/libchart/classes/view/color/Color.php @@ -1,6 +1,6 @@ red = (int) $red; $this->green = (int) $green; $this->blue = (int) $blue; diff --git a/WEB-INF/lib/libchart/classes/view/color/ColorSet.php b/WEB-INF/lib/libchart/classes/view/color/ColorSet.php index 9876ef08c..fc6e9f694 100644 --- a/WEB-INF/lib/libchart/classes/view/color/ColorSet.php +++ b/WEB-INF/lib/libchart/classes/view/color/ColorSet.php @@ -1,6 +1,6 @@ colorList = $colorList; $this->shadowColorList = array(); diff --git a/WEB-INF/lib/libchart/classes/view/color/Palette.php b/WEB-INF/lib/libchart/classes/view/color/Palette.php index 48189b971..4f3d9d360 100644 --- a/WEB-INF/lib/libchart/classes/view/color/Palette.php +++ b/WEB-INF/lib/libchart/classes/view/color/Palette.php @@ -1,6 +1,6 @@ red = new Color(255, 0, 0); // Colors for the horizontal and vertical axis diff --git a/WEB-INF/lib/libchart/classes/view/plot/Plot.php b/WEB-INF/lib/libchart/classes/view/plot/Plot.php index a57d4ffbb..88e42dfb3 100644 --- a/WEB-INF/lib/libchart/classes/view/plot/Plot.php +++ b/WEB-INF/lib/libchart/classes/view/plot/Plot.php @@ -1,6 +1,6 @@ width = $width; $this->height = $height; diff --git a/WEB-INF/lib/libchart/classes/view/primitive/Padding.php b/WEB-INF/lib/libchart/classes/view/primitive/Padding.php index 9cade66c9..6c7c634fb 100644 --- a/WEB-INF/lib/libchart/classes/view/primitive/Padding.php +++ b/WEB-INF/lib/libchart/classes/view/primitive/Padding.php @@ -1,6 +1,6 @@ top = $top; if ($right == null) { $this->right = $top; diff --git a/WEB-INF/lib/libchart/classes/view/primitive/Primitive.php b/WEB-INF/lib/libchart/classes/view/primitive/Primitive.php index b6b26bd80..f88c99e02 100644 --- a/WEB-INF/lib/libchart/classes/view/primitive/Primitive.php +++ b/WEB-INF/lib/libchart/classes/view/primitive/Primitive.php @@ -1,6 +1,6 @@ img = $img; } diff --git a/WEB-INF/lib/libchart/classes/view/primitive/Rectangle.php b/WEB-INF/lib/libchart/classes/view/primitive/Rectangle.php index 402214ea7..5e31e29a0 100644 --- a/WEB-INF/lib/libchart/classes/view/primitive/Rectangle.php +++ b/WEB-INF/lib/libchart/classes/view/primitive/Rectangle.php @@ -1,6 +1,6 @@ x1 = $x1; $this->y1 = $y1; $this->x2 = $x2; diff --git a/WEB-INF/lib/libchart/classes/view/text/Text.php b/WEB-INF/lib/libchart/classes/view/text/Text.php index eeef1cfb0..f66ea7d3e 100644 --- a/WEB-INF/lib/libchart/classes/view/text/Text.php +++ b/WEB-INF/lib/libchart/classes/view/text/Text.php @@ -1,6 +1,6 @@ diff --git a/WEB-INF/lib/mail/Mailer.class.php b/WEB-INF/lib/mail/Mailer.class.php index a52e3c98a..4c7eb0134 100644 --- a/WEB-INF/lib/mail/Mailer.class.php +++ b/WEB-INF/lib/mail/Mailer.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ class Mailer { @@ -33,6 +33,7 @@ class Mailer { var $mSender; var $mReceiver; var $mReceiverCC; + var $mReceiverBCC; function __construct($type='mail') { $this->mMailMode = $type; @@ -58,6 +59,10 @@ function setReceiverCC($value) { $this->mReceiverCC = $value; } + function setReceiverBCC($value) { + $this->mReceiverBCC = $value; + } + function setSender($value) { $this->mSender = $value; } @@ -68,6 +73,7 @@ function send($subject, $data) { $headers = array('From' => $this->mSender, 'To' => $this->mReceiver); if (isset($this->mReceiverCC)) $headers = array_merge($headers, array('CC' => $this->mReceiverCC)); + if (isset($this->mReceiverBCC)) $headers = array_merge($headers, array('BCC' => $this->mReceiverBCC)); $headers = array_merge($headers, array( 'Subject' => $subject, 'MIME-Version' => '1.0', @@ -84,18 +90,22 @@ function send($subject, $data) { break; case 'smtp': - // Mail_smtp does not do CC -> recipients conversion + // Mail_smtp does not do CC or BCC -> recipients conversion. if (!empty($this->mReceiverCC)) { // make exactly one space after a comma $recipients .= ', ' . preg_replace('/,[[:space:]]+/', ', ', $this->mReceiverCC); } + if (!empty($this->mReceiverBCC)) { + // make exactly one space after a comma + $recipients .= ', ' . preg_replace('/,[[:space:]]+/', ', ', $this->mReceiverBCC); + } $host = defined('MAIL_SMTP_HOST') ? MAIL_SMTP_HOST : 'localhost'; $port = defined('MAIL_SMTP_PORT') ? MAIL_SMTP_PORT : '25'; $username = defined('MAIL_SMTP_USER') ? MAIL_SMTP_USER : null; $password = defined('MAIL_SMTP_PASSWORD') ? MAIL_SMTP_PASSWORD : null; - $auth = (defined('MAIL_SMTP_AUTH') && isTrue(MAIL_SMTP_AUTH)) ? true : false; - $debug = (defined('MAIL_SMTP_DEBUG') && isTrue(MAIL_SMTP_DEBUG)) ? true : false; + $auth = isTrue('MAIL_SMTP_AUTH'); + $debug = isTrue('MAIL_SMTP_DEBUG'); $mail = Mail::factory('smtp', array ('host' => $host, 'port' => $port, @@ -106,7 +116,7 @@ function send($subject, $data) { break; } - if (defined('MAIL_SMTP_DEBUG') && isTrue(MAIL_SMTP_DEBUG)) + if (isTrue('MAIL_SMTP_DEBUG')) PEAR::setErrorHandling(PEAR_ERROR_PRINT); $res = $mail->send($recipients, $headers, $data); diff --git a/WEB-INF/lib/pear/MDB2.php b/WEB-INF/lib/pear/MDB2.php index 45c0802df..24dd84d21 100644 --- a/WEB-INF/lib/pear/MDB2.php +++ b/WEB-INF/lib/pear/MDB2.php @@ -823,9 +823,11 @@ static function parseDSN($dsn) $parsed['dbsyntax'] = $str; } - if (!count($dsn)) { - return $parsed; - } + if (is_array($dsn) || $dsn instanceof Countable) { // Added by Nik on Oct 19, 2020 to avoid flooding logs with warnings. + if (!count($dsn)) { + return $parsed; + } + } // Ideally, this should be fixed in MDB2 package. // Get (if found): username and password // $dsn => username:password@protocol+hostspec/database diff --git a/WEB-INF/lib/smarty/Autoloader.php b/WEB-INF/lib/smarty/Autoloader.php new file mode 100644 index 000000000..da7e32abf --- /dev/null +++ b/WEB-INF/lib/smarty/Autoloader.php @@ -0,0 +1,111 @@ + 'Smarty.class.php'); + + /** + * Registers Smarty_Autoloader backward compatible to older installations. + * + * @param bool $prepend Whether to prepend the autoloader or not. + */ + public static function registerBC($prepend = false) + { + /** + * register the class autoloader + */ + if (!defined('SMARTY_SPL_AUTOLOAD')) { + define('SMARTY_SPL_AUTOLOAD', 0); + } + if (SMARTY_SPL_AUTOLOAD + && set_include_path(get_include_path() . PATH_SEPARATOR . SMARTY_SYSPLUGINS_DIR) !== false + ) { + $registeredAutoLoadFunctions = spl_autoload_functions(); + if (!isset($registeredAutoLoadFunctions[ 'spl_autoload' ])) { + spl_autoload_register(); + } + } else { + self::register($prepend); + } + } + + /** + * Registers Smarty_Autoloader as an SPL autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not. + */ + public static function register($prepend = false) + { + self::$SMARTY_DIR = defined('SMARTY_DIR') ? SMARTY_DIR : __DIR__ . DIRECTORY_SEPARATOR; + self::$SMARTY_SYSPLUGINS_DIR = defined('SMARTY_SYSPLUGINS_DIR') ? SMARTY_SYSPLUGINS_DIR : + self::$SMARTY_DIR . 'sysplugins' . DIRECTORY_SEPARATOR; + spl_autoload_register(array(__CLASS__, 'autoload'), true, $prepend); + } + + /** + * Handles auto loading of classes. + * + * @param string $class A class name. + */ + public static function autoload($class) + { + if ($class[ 0 ] !== 'S' || strpos($class, 'Smarty') !== 0) { + return; + } + $_class = smarty_strtolower_ascii($class); + if (isset(self::$rootClasses[ $_class ])) { + $file = self::$SMARTY_DIR . self::$rootClasses[ $_class ]; + if (is_file($file)) { + include $file; + } + } else { + $file = self::$SMARTY_SYSPLUGINS_DIR . $_class . '.php'; + if (is_file($file)) { + include $file; + } + } + return; + } +} diff --git a/WEB-INF/lib/smarty/Smarty.class.php b/WEB-INF/lib/smarty/Smarty.class.php index feb88171e..5d2e3a4b4 100644 --- a/WEB-INF/lib/smarty/Smarty.class.php +++ b/WEB-INF/lib/smarty/Smarty.class.php @@ -1,815 +1,1406 @@ - * @author Uwe Tews - * @package Smarty - * @version 3.0.7 - */ - -/** - * define shorthand directory separator constant + * + * @link https://www.smarty.net/ + * @copyright 2018 New Digital Group, Inc. + * @copyright 2018 Uwe Tews + * @author Monte Ohrt + * @author Uwe Tews + * @author Rodney Rehm + * @package Smarty */ -if (!defined('DS')) { - define('DS', DIRECTORY_SEPARATOR); -} - /** * set SMARTY_DIR to absolute path to Smarty library files. * Sets SMARTY_DIR only if user application has not already defined it. */ if (!defined('SMARTY_DIR')) { - define('SMARTY_DIR', dirname(__FILE__) . DS); -} - + /** + * + */ + define('SMARTY_DIR', __DIR__ . DIRECTORY_SEPARATOR); +} /** * set SMARTY_SYSPLUGINS_DIR to absolute path to Smarty internal plugins. * Sets SMARTY_SYSPLUGINS_DIR only if user application has not already defined it. */ if (!defined('SMARTY_SYSPLUGINS_DIR')) { - define('SMARTY_SYSPLUGINS_DIR', SMARTY_DIR . 'sysplugins' . DS); -} + /** + * + */ + define('SMARTY_SYSPLUGINS_DIR', SMARTY_DIR . 'sysplugins' . DIRECTORY_SEPARATOR); +} if (!defined('SMARTY_PLUGINS_DIR')) { - define('SMARTY_PLUGINS_DIR', SMARTY_DIR . 'plugins' . DS); -} -if (!defined('SMARTY_RESOURCE_CHAR_SET')) { - define('SMARTY_RESOURCE_CHAR_SET', 'UTF-8'); -} -if (!defined('SMARTY_RESOURCE_DATE_FORMAT')) { - define('SMARTY_RESOURCE_DATE_FORMAT', '%b %e, %Y'); -} + /** + * + */ + define('SMARTY_PLUGINS_DIR', SMARTY_DIR . 'plugins' . DIRECTORY_SEPARATOR); +} +if (!defined('SMARTY_MBSTRING')) { + /** + * + */ + define('SMARTY_MBSTRING', function_exists('mb_get_info')); +} + +/** + * Load helper functions + */ +if (!defined('SMARTY_HELPER_FUNCTIONS_LOADED')) { + include __DIR__ . '/functions.php'; +} + +/** + * Load Smarty_Autoloader + */ +if (!class_exists('Smarty_Autoloader')) { + include __DIR__ . '/bootstrap.php'; +} /** - * register the class autoloader + * Load always needed external class files */ -if (!defined('SMARTY_SPL_AUTOLOAD')) { - define('SMARTY_SPL_AUTOLOAD', 0); -} - -if (SMARTY_SPL_AUTOLOAD && set_include_path(get_include_path() . PATH_SEPARATOR . SMARTY_SYSPLUGINS_DIR) !== false) { - $registeredAutoLoadFunctions = spl_autoload_functions(); - if (!isset($registeredAutoLoadFunctions['spl_autoload'])) { - spl_autoload_register(); - } -} else { - spl_autoload_register('smartyAutoload'); -} +require_once SMARTY_SYSPLUGINS_DIR . 'smarty_internal_data.php'; +require_once SMARTY_SYSPLUGINS_DIR . 'smarty_internal_extension_handler.php'; +require_once SMARTY_SYSPLUGINS_DIR . 'smarty_internal_templatebase.php'; +require_once SMARTY_SYSPLUGINS_DIR . 'smarty_internal_template.php'; +require_once SMARTY_SYSPLUGINS_DIR . 'smarty_resource.php'; +require_once SMARTY_SYSPLUGINS_DIR . 'smarty_variable.php'; +require_once SMARTY_SYSPLUGINS_DIR . 'smarty_template_source.php'; +require_once SMARTY_SYSPLUGINS_DIR . 'smarty_template_resource_base.php'; +require_once SMARTY_SYSPLUGINS_DIR . 'smarty_internal_resource_file.php'; /** * This is the main Smarty class + * + * @package Smarty + * + * The following methods will be dynamically loaded by the extension handler when they are called. + * They are located in a corresponding Smarty_Internal_Method_xxxx class + * + * @method int clearAllCache(int $exp_time = null, string $type = null) + * @method int clearCache(string $template_name, string $cache_id = null, string $compile_id = null, int $exp_time = null, string $type = null) + * @method int compileAllTemplates(string $extension = '.tpl', bool $force_compile = false, int $time_limit = 0, $max_errors = null) + * @method int compileAllConfig(string $extension = '.conf', bool $force_compile = false, int $time_limit = 0, $max_errors = null) + * @method int clearCompiledTemplate($resource_name = null, $compile_id = null, $exp_time = null) */ -class Smarty extends Smarty_Internal_Data { - /** - * constant definitions - */ - // smarty version - const SMARTY_VERSION = 'Smarty-3.0.7'; - //define variable scopes - const SCOPE_LOCAL = 0; - const SCOPE_PARENT = 1; - const SCOPE_ROOT = 2; - const SCOPE_GLOBAL = 3; - // define caching modes - const CACHING_OFF = 0; - const CACHING_LIFETIME_CURRENT = 1; - const CACHING_LIFETIME_SAVED = 2; - /** modes for handling of "" tags in templates. **/ - const PHP_PASSTHRU = 0; //-> print tags as plain text - const PHP_QUOTE = 1; //-> escape tags as entities - const PHP_REMOVE = 2; //-> escape tags as entities - const PHP_ALLOW = 3; //-> escape tags as entities - // filter types - const FILTER_POST = 'post'; - const FILTER_PRE = 'pre'; - const FILTER_OUTPUT = 'output'; - const FILTER_VARIABLE = 'variable'; - // plugin types - const PLUGIN_FUNCTION = 'function'; - const PLUGIN_BLOCK = 'block'; - const PLUGIN_COMPILER = 'compiler'; - const PLUGIN_MODIFIER = 'modifier'; - - /** - * static variables - */ - // assigned global tpl vars - static $global_tpl_vars = array(); - - /** - * variables - */ - // auto literal on delimiters with whitspace - public $auto_literal = true; - // display error on not assigned variables - public $error_unassigned = false; - // template directory - public $template_dir = null; - // default template handler - public $default_template_handler_func = null; - // compile directory - public $compile_dir = null; - // plugins directory - public $plugins_dir = null; - // cache directory - public $cache_dir = null; - // config directory - public $config_dir = null; - // force template compiling? - public $force_compile = false; - // check template for modifications? - public $compile_check = true; - // locking concurrent compiles - public $compile_locking = true; - // use sub dirs for compiled/cached files? - public $use_sub_dirs = false; - // compile_error? - public $compile_error = false; - // caching enabled - public $caching = false; - // merge compiled includes - public $merge_compiled_includes = false; - // cache lifetime - public $cache_lifetime = 3600; - // force cache file creation - public $force_cache = false; - // cache_id - public $cache_id = null; - // compile_id - public $compile_id = null; - // template delimiters +class Smarty extends Smarty_Internal_TemplateBase +{ + /** + * smarty version + */ + const SMARTY_VERSION = '4.3.0'; + /** + * define variable scopes + */ + const SCOPE_LOCAL = 1; + const SCOPE_PARENT = 2; + const SCOPE_TPL_ROOT = 4; + const SCOPE_ROOT = 8; + const SCOPE_SMARTY = 16; + const SCOPE_GLOBAL = 32; + /** + * define caching modes + */ + const CACHING_OFF = 0; + const CACHING_LIFETIME_CURRENT = 1; + const CACHING_LIFETIME_SAVED = 2; + /** + * define constant for clearing cache files be saved expiration dates + */ + const CLEAR_EXPIRED = -1; + /** + * define compile check modes + */ + const COMPILECHECK_OFF = 0; + const COMPILECHECK_ON = 1; + const COMPILECHECK_CACHEMISS = 2; + /** + * define debug modes + */ + const DEBUG_OFF = 0; + const DEBUG_ON = 1; + const DEBUG_INDIVIDUAL = 2; + + /** + * filter types + */ + const FILTER_POST = 'post'; + const FILTER_PRE = 'pre'; + const FILTER_OUTPUT = 'output'; + const FILTER_VARIABLE = 'variable'; + /** + * plugin types + */ + const PLUGIN_FUNCTION = 'function'; + const PLUGIN_BLOCK = 'block'; + const PLUGIN_COMPILER = 'compiler'; + const PLUGIN_MODIFIER = 'modifier'; + const PLUGIN_MODIFIERCOMPILER = 'modifiercompiler'; + + /** + * assigned global tpl vars + */ + public static $global_tpl_vars = array(); + + /** + * Flag denoting if Multibyte String functions are available + */ + public static $_MBSTRING = SMARTY_MBSTRING; + + /** + * The character set to adhere to (e.g. "UTF-8") + */ + public static $_CHARSET = SMARTY_MBSTRING ? 'UTF-8' : 'ISO-8859-1'; + + /** + * The date format to be used internally + * (accepts date() and strftime()) + */ + public static $_DATE_FORMAT = '%b %e, %Y'; + + /** + * Flag denoting if PCRE should run in UTF-8 mode + */ + public static $_UTF8_MODIFIER = 'u'; + + /** + * Flag denoting if operating system is windows + */ + public static $_IS_WINDOWS = false; + + /** + * auto literal on delimiters with whitespace + * + * @var boolean + */ + public $auto_literal = true; + + /** + * display error on not assigned variables + * + * @var boolean + */ + public $error_unassigned = false; + + /** + * look up relative file path in include_path + * + * @var boolean + */ + public $use_include_path = false; + + /** + * flag if template_dir is normalized + * + * @var bool + */ + public $_templateDirNormalized = false; + + /** + * joined template directory string used in cache keys + * + * @var string + */ + public $_joined_template_dir = null; + + /** + * flag if config_dir is normalized + * + * @var bool + */ + public $_configDirNormalized = false; + + /** + * joined config directory string used in cache keys + * + * @var string + */ + public $_joined_config_dir = null; + + /** + * default template handler + * + * @var callable + */ + public $default_template_handler_func = null; + + /** + * default config handler + * + * @var callable + */ + public $default_config_handler_func = null; + + /** + * default plugin handler + * + * @var callable + */ + public $default_plugin_handler_func = null; + + /** + * flag if template_dir is normalized + * + * @var bool + */ + public $_compileDirNormalized = false; + + /** + * flag if plugins_dir is normalized + * + * @var bool + */ + public $_pluginsDirNormalized = false; + + /** + * flag if template_dir is normalized + * + * @var bool + */ + public $_cacheDirNormalized = false; + + /** + * force template compiling? + * + * @var boolean + */ + public $force_compile = false; + + /** + * use sub dirs for compiled/cached files? + * + * @var boolean + */ + public $use_sub_dirs = false; + + /** + * allow ambiguous resources (that are made unique by the resource handler) + * + * @var boolean + */ + public $allow_ambiguous_resources = false; + + /** + * merge compiled includes + * + * @var boolean + */ + public $merge_compiled_includes = false; + + /* + * flag for behaviour when extends: resource and {extends} tag are used simultaneous + * if false disable execution of {extends} in templates called by extends resource. + * (behaviour as versions < 3.1.28) + * + * @var boolean + */ + public $extends_recursion = true; + + /** + * force cache file creation + * + * @var boolean + */ + public $force_cache = false; + + /** + * template left-delimiter + * + * @var string + */ public $left_delimiter = "{"; - public $right_delimiter = "}"; - // security + + /** + * template right-delimiter + * + * @var string + */ + public $right_delimiter = "}"; + + /** + * array of strings which shall be treated as literal by compiler + * + * @var array string + */ + public $literals = array(); + + /** + * class name + * This should be instance of Smarty_Security. + * + * @var string + * @see Smarty_Security + */ public $security_class = 'Smarty_Security'; + + /** + * implementation of security class + * + * @var Smarty_Security + */ public $security_policy = null; - public $php_handling = self::PHP_PASSTHRU; - public $allow_php_tag = false; + + /** + * controls if the php template file resource is allowed + * + * @var bool + */ public $allow_php_templates = false; - public $direct_access_security = true; - public $trusted_dir = array(); - // debug mode + + /** + * debug mode + * Setting this to true enables the debug-console. + * + * @var boolean + */ public $debugging = false; + + /** + * This determines if debugging is enable-able from the browser. + *
    + *
  • NONE => no debugging control allowed
  • + *
  • URL => enable debugging when SMARTY_DEBUG is found in the URL.
  • + *
+ * + * @var string + */ public $debugging_ctrl = 'NONE'; + + /** + * Name of debugging URL-param. + * Only used when $debugging_ctrl is set to 'URL'. + * The name of the URL-parameter that activates debugging. + * + * @var string + */ public $smarty_debug_id = 'SMARTY_DEBUG'; - public $debug_tpl = null; - // When set, smarty does uses this value as error_reporting-level. - public $error_reporting = null; - // config var settings - public $config_overwrite = true; //Controls whether variables with the same name overwrite each other. - public $config_booleanize = true; //Controls whether config values of on/true/yes and off/false/no get converted to boolean - public $config_read_hidden = false; //Controls whether hidden config sections/vars are read from the file. - // config vars - public $config_vars = array(); - // assigned tpl vars - public $tpl_vars = array(); - // dummy parent object - public $parent = null; - // global template functions - public $template_functions = array(); - // resource type used if none given - public $default_resource_type = 'file'; - // caching type - public $caching_type = 'file'; - // internal cache resource types - public $cache_resource_types = array('file'); - // internal config properties - public $properties = array(); - // config type - public $default_config_type = 'file'; - // cached template objects - public $template_objects = null; - // check If-Modified-Since headers - public $cache_modified_check = false; - // registered plugins - public $registered_plugins = array(); - // plugin search order - public $plugin_search_order = array('function', 'block', 'compiler', 'class'); - // registered objects - public $registered_objects = array(); - // registered classes - public $registered_classes = array(); - // registered filters - public $registered_filters = array(); - // registered resources - public $registered_resources = array(); - // autoload filter - public $autoload_filters = array(); - // status of filter on variable output - public $variable_filter = true; - // default modifier - public $default_modifiers = array(); - // global internal smarty vars - static $_smarty_vars = array(); - // start time for execution time calculation - public $start_time = 0; - // default file permissions - public $_file_perms = 0644; - // default dir permissions - public $_dir_perms = 0771; - // block tag hierarchy - public $_tag_stack = array(); - // flag if {block} tag is compiled for template inheritance - public $inheritance = false; - // generate deprecated function call notices? - public $deprecation_notices = true; - // Smarty 2 BC - public $_version = self::SMARTY_VERSION; - // self pointer to Smarty object - public $smarty; - - /** - * Class constructor, initializes basic smarty properties + + /** + * Path of debug template. + * + * @var string */ - public function __construct() - { - // selfpointer need by some other class methods - $this->smarty = $this; - if (is_callable('mb_internal_encoding')) { - mb_internal_encoding(SMARTY_RESOURCE_CHAR_SET); - } - $this->start_time = microtime(true); - // set default dirs - $this->template_dir = array('.' . DS . 'templates' . DS); - $this->compile_dir = '.' . DS . 'templates_c' . DS; - $this->plugins_dir = array(SMARTY_PLUGINS_DIR); - $this->cache_dir = '.' . DS . 'cache' . DS; - $this->config_dir = '.' . DS . 'configs' . DS; - $this->debug_tpl = SMARTY_DIR . 'debug.tpl'; - if (isset($_SERVER['SCRIPT_NAME'])) { - $this->assignGlobal('SCRIPT_NAME', $_SERVER['SCRIPT_NAME']); - } - } - - /** - * Class destructor - */ - public function __destruct() - { - } - - /** - * fetches a rendered Smarty template - * - * @param string $template the resource handle of the template file or template object - * @param mixed $cache_id cache id to be used with this template - * @param mixed $compile_id compile id to be used with this template - * @param object $ |null $parent next higher level of Smarty variables - * @return string rendered template output - */ - public function fetch($template, $cache_id = null, $compile_id = null, $parent = null, $display = false) - { - if (!empty($cache_id) && is_object($cache_id)) { - $parent = $cache_id; - $cache_id = null; - } - if ($parent === null) { - // get default Smarty data object - $parent = $this; - } - // create template object if necessary - ($template instanceof $this->template_class)? $_template = $template : - $_template = $this->createTemplate ($template, $cache_id, $compile_id, $parent, false); - if (isset($this->error_reporting)) { - $_smarty_old_error_level = error_reporting($this->error_reporting); - } - // check URL debugging control - if (!$this->debugging && $this->debugging_ctrl == 'URL') { - if (isset($_SERVER['QUERY_STRING'])) { - $_query_string = $_SERVER['QUERY_STRING']; - } else { - $_query_string = ''; - } - if (false !== strpos($_query_string, $this->smarty_debug_id)) { - if (false !== strpos($_query_string, $this->smarty_debug_id . '=on')) { - // enable debugging for this browser session - setcookie('SMARTY_DEBUG', true); - $this->debugging = true; - } elseif (false !== strpos($_query_string, $this->smarty_debug_id . '=off')) { - // disable debugging for this browser session - setcookie('SMARTY_DEBUG', false); - $this->debugging = false; - } else { - // enable debugging for this page - $this->debugging = true; - } - } else { - if (isset($_COOKIE['SMARTY_DEBUG'])) { - $this->debugging = true; - } - } - } - // obtain data for cache modified check - if ($this->cache_modified_check && $this->caching && $display) { - $_isCached = $_template->isCached() && !$_template->has_nocache_code; - if ($_isCached) { - $_gmt_mtime = gmdate('D, d M Y H:i:s', $_template->getCachedTimestamp()) . ' GMT'; - } else { - $_gmt_mtime = ''; - } - } - // return rendered template - if ((!$this->caching || $_template->resource_object->isEvaluated) && (isset($this->autoload_filters['output']) || isset($this->registered_filters['output']))) { - $_output = Smarty_Internal_Filter_Handler::runFilter('output', $_template->getRenderedTemplate(), $_template); - } else { - $_output = $_template->getRenderedTemplate(); - } - $_template->rendered_content = null; - if (isset($this->error_reporting)) { - error_reporting($_smarty_old_error_level); - } - // display or fetch - if ($display) { - if ($this->caching && $this->cache_modified_check) { - $_last_modified_date = @substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], 'GMT') + 3); - if ($_isCached && $_gmt_mtime == $_last_modified_date) { - if (php_sapi_name() == 'cgi') - header('Status: 304 Not Modified'); - else - header('HTTP/1.1 304 Not Modified'); - } else { - header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $_template->getCachedTimestamp()) . ' GMT'); - echo $_output; - } - } else { - echo $_output; - } - // debug output - if ($this->debugging) { - Smarty_Internal_Debug::display_debug($this); - } - return; - } else { - // return fetched content - return $_output; - } - } + public $debug_tpl = null; /** - * displays a Smarty template - * - * @param string $ |object $template the resource handle of the template file or template object - * @param mixed $cache_id cache id to be used with this template - * @param mixed $compile_id compile id to be used with this template - * @param object $parent next higher level of Smarty variables + * When set, smarty uses this value as error_reporting-level. + * + * @var int */ - public function display($template, $cache_id = null, $compile_id = null, $parent = null) - { - // display template - $this->fetch ($template, $cache_id, $compile_id, $parent, true); - } + public $error_reporting = null; /** - * test if cache i valid - * - * @param string $ |object $template the resource handle of the template file or template object - * @param mixed $cache_id cache id to be used with this template - * @param mixed $compile_id compile id to be used with this template - * @param object $parent next higher level of Smarty variables - * @return boolean cache status + * Controls whether variables with the same name overwrite each other. + * + * @var boolean */ - public function isCached($template, $cache_id = null, $compile_id = null, $parent = null) - { - if ($parent === null) { - $parent = $this; - } - if (!($template instanceof $this->template_class)) { - $template = $this->createTemplate ($template, $cache_id, $compile_id, $parent, false); - } - // return cache status of template - return $template->isCached(); - } + public $config_overwrite = true; /** - * creates a data object - * - * @param object $parent next higher level of Smarty variables - * @returns object data object + * Controls whether config values of on/true/yes and off/false/no get converted to boolean. + * + * @var boolean */ - public function createData($parent = null) - { - return new Smarty_Data($parent, $this); - } + public $config_booleanize = true; /** - * creates a template object - * - * @param string $template the resource handle of the template file - * @param mixed $cache_id cache id to be used with this template - * @param mixed $compile_id compile id to be used with this template - * @param object $parent next higher level of Smarty variables - * @param boolean $do_clone flag is Smarty object shall be cloned - * @returns object template object + * Controls whether hidden config sections/vars are read from the file. + * + * @var boolean */ - public function createTemplate($template, $cache_id = null, $compile_id = null, $parent = null, $do_clone = true) - { - if (!empty($cache_id) && (is_object($cache_id) || is_array($cache_id))) { - $parent = $cache_id; - $cache_id = null; - } - if (!empty($parent) && is_array($parent)) { - $data = $parent; - $parent = null; - } else { - $data = null; - } - if (!is_object($template)) { - // we got a template resource - // already in template cache? - $_templateId = sha1($template . $cache_id . $compile_id); - if (isset($this->template_objects[$_templateId]) && $this->caching) { - // return cached template object - $tpl = $this->template_objects[$_templateId]; - } else { - // create new template object - if ($do_clone) { - $tpl = new $this->template_class($template, clone $this, $parent, $cache_id, $compile_id); - } else { - $tpl = new $this->template_class($template, $this, $parent, $cache_id, $compile_id); - } - } - } else { - // just return a copy of template class - $tpl = $template; - } - // fill data if present - if (!empty($data) && is_array($data)) { - // set up variable values - foreach ($data as $_key => $_val) { - $tpl->tpl_vars[$_key] = new Smarty_variable($_val); - } - } - return $tpl; - } - - + public $config_read_hidden = false; /** - * Check if a template resource exists - * - * @param string $resource_name template name - * @return boolean status - */ - function templateExists($resource_name) - { - // create template object - $save = $this->template_objects; - $tpl = new $this->template_class($resource_name, $this); - // check if it does exists - $result = $tpl->isExisting(); - $this->template_objects = $save; - return $result; - } - - /** - * Returns a single or all global variables - * - * @param object $smarty - * @param string $varname variable name or null - * @return string variable value or or array of variables - */ - function getGlobal($varname = null) - { - if (isset($varname)) { - if (isset(self::$global_tpl_vars[$varname])) { - return self::$global_tpl_vars[$varname]->value; - } else { - return ''; - } - } else { - $_result = array(); - foreach (self::$global_tpl_vars AS $key => $var) { - $_result[$key] = $var->value; - } - return $_result; - } - } - - /** - * Empty cache folder - * - * @param integer $exp_time expiration time - * @param string $type resource type - * @return integer number of cache files deleted - */ - function clearAllCache($exp_time = null, $type = null) - { - // load cache resource and call clearAll - return $this->loadCacheResource($type)->clearAll($exp_time); - } - - /** - * Empty cache for a specific template - * - * @param string $template_name template name - * @param string $cache_id cache id - * @param string $compile_id compile id - * @param integer $exp_time expiration time - * @param string $type resource type - * @return integer number of cache files deleted - */ - function clearCache($template_name, $cache_id = null, $compile_id = null, $exp_time = null, $type = null) - { - // load cache resource and call clear - return $this->loadCacheResource($type)->clear($template_name, $cache_id, $compile_id, $exp_time); - } + * locking concurrent compiles + * + * @var boolean + */ + public $compile_locking = true; /** - * Loads security class and enables security + * Controls whether cache resources should use locking mechanism + * + * @var boolean */ - public function enableSecurity($security_class = null) - { - if ($security_class instanceof Smarty_Security) { - $this->security_policy = $security_class; - return; - } - if ($security_class == null) { - $security_class = $this->security_class; - } - if (class_exists($security_class)) { - $this->security_policy = new $security_class($this); - } else { - throw new SmartyException("Security class '$security_class' is not defined"); - } - } + public $cache_locking = false; /** - * Disable security + * seconds to wait for acquiring a lock before ignoring the write lock + * + * @var float */ - public function disableSecurity() - { - $this->security_policy = null; - } + public $locking_timeout = 10; /** - * Loads cache resource. - * - * @param string $type cache resource type - * @return object of cache resource - */ - public function loadCacheResource($type = null) { - if (!isset($type)) { - $type = $this->caching_type; - } - if (in_array($type, $this->cache_resource_types)) { - $cache_resource_class = 'Smarty_Internal_CacheResource_' . ucfirst($type); - return new $cache_resource_class($this); - } - else { - // try plugins dir - $cache_resource_class = 'Smarty_CacheResource_' . ucfirst($type); - if ($this->loadPlugin($cache_resource_class)) { - return new $cache_resource_class($this); - } - else { - throw new SmartyException("Unable to load cache resource '{$type}'"); - } - } - } + * resource type used if none given + * Must be an valid key of $registered_resources. + * + * @var string + */ + public $default_resource_type = 'file'; + /** + * caching type + * Must be an element of $cache_resource_types. + * + * @var string + */ + public $caching_type = 'file'; /** - * Set template directory - * - * @param string $ |array $template_dir folder(s) of template sorces + * config type + * + * @var string */ - public function setTemplateDir($template_dir) - { - $this->template_dir = (array)$template_dir; - return; - } + public $default_config_type = 'file'; /** - * Adds template directory(s) to existing ones - * - * @param string $ |array $template_dir folder(s) of template sources + * check If-Modified-Since headers + * + * @var boolean */ - public function addTemplateDir($template_dir) - { - $this->template_dir = array_unique(array_merge((array)$this->template_dir, (array)$template_dir)); - return; - } - + public $cache_modified_check = false; + /** - * Adds directory of plugin files - * - * @param object $smarty - * @param string $ |array $ plugins folder - * @return + * registered plugins + * + * @var array */ - function addPluginsDir($plugins_dir) - { - $this->plugins_dir = array_unique(array_merge((array)$this->plugins_dir, (array)$plugins_dir)); - return; - } + public $registered_plugins = array(); + /** + * registered objects + * + * @var array + */ + public $registered_objects = array(); /** - * return a reference to a registered object - * - * @param string $name object name - * @return object + * registered classes + * + * @var array */ - function getRegisteredObject($name) - { - if (!isset($this->registered_objects[$name])) - throw new SmartyException("'$name' is not a registered object"); + public $registered_classes = array(); - if (!is_object($this->registered_objects[$name][0])) - throw new SmartyException("registered '$name' is not an object"); + /** + * registered filters + * + * @var array + */ + public $registered_filters = array(); - return $this->registered_objects[$name][0]; - } + /** + * registered resources + * + * @var array + */ + public $registered_resources = array(); + /** + * registered cache resources + * + * @var array + */ + public $registered_cache_resources = array(); /** - * return name of debugging template - * - * @return string + * autoload filter + * + * @var array */ - function getDebugTemplate() - { - return $this->debug_tpl; - } + public $autoload_filters = array(); /** - * set the debug template - * - * @param string $tpl_name - * @return bool + * default modifier + * + * @var array */ - function setDebugTemplate($tpl_name) - { - return $this->debug_tpl = $tpl_name; - } + public $default_modifiers = array(); /** - * Takes unknown classes and loads plugin files for them - * class name format: Smarty_PluginType_PluginName - * plugin filename format: plugintype.pluginname.php - * - * @param string $plugin_name class plugin name to load - * @return string |boolean filepath of loaded file or false + * autoescape variable output + * + * @var boolean */ - public function loadPlugin($plugin_name, $check = true) - { - // if function or class exists, exit silently (already loaded) - if ($check && (is_callable($plugin_name) || class_exists($plugin_name, false))) - return true; - // Plugin name is expected to be: Smarty_[Type]_[Name] - $_plugin_name = strtolower($plugin_name); - $_name_parts = explode('_', $_plugin_name, 3); - // class name must have three parts to be valid plugin - if (count($_name_parts) < 3 || $_name_parts[0] !== 'smarty') { - throw new SmartyException("plugin {$plugin_name} is not a valid name format"); - return false; - } - // if type is "internal", get plugin from sysplugins - if ($_name_parts[1] == 'internal') { - $file = SMARTY_SYSPLUGINS_DIR . $_plugin_name . '.php'; - if (file_exists($file)) { - require_once($file); - return $file; - } else { - return false; - } - } - // plugin filename is expected to be: [type].[name].php - $_plugin_filename = "{$_name_parts[1]}.{$_name_parts[2]}.php"; - // loop through plugin dirs and find the plugin - foreach((array)$this->plugins_dir as $_plugin_dir) { - if (strpos('/\\', substr($_plugin_dir, -1)) === false) { - $_plugin_dir .= DS; - } - $file = $_plugin_dir . $_plugin_filename; - if (file_exists($file)) { - require_once($file); - return $file; - } - } - // no plugin loaded - return false; - } - - /** - * clean up properties on cloned object - */ - public function __clone() - { - // clear config vars - $this->config_vars = array(); - // clear assigned tpl vars - $this->tpl_vars = array(); - // clear objects for external methods - unset($this->register); - unset($this->filter); - } - - - /** - * Handle unknown class methods - * - * @param string $name unknown methode name - * @param array $args aurgument array - */ - public function __call($name, $args) - { - static $camel_func; - if (!isset($camel_func)) - $camel_func = create_function('$c', 'return "_" . strtolower($c[1]);'); - // see if this is a set/get for a property - $first3 = strtolower(substr($name, 0, 3)); - if (in_array($first3, array('set', 'get')) && substr($name, 3, 1) !== '_') { - // try to keep case correct for future PHP 6.0 case-sensitive class methods - // lcfirst() not available < PHP 5.3.0, so improvise - $property_name = strtolower(substr($name, 3, 1)) . substr($name, 4); - // convert camel case to underscored name - $property_name = preg_replace_callback('/([A-Z])/', $camel_func, $property_name); - if (!property_exists($this, $property_name)) { - throw new SmartyException("property '$property_name' does not exist."); - return false; - } - if ($first3 == 'get') - return $this->$property_name; - else - return $this->$property_name = $args[0]; - } - // Smarty Backward Compatible wrapper - if (strpos($name,'_') !== false) { - if (!isset($this->wrapper)) { - $this->wrapper = new Smarty_Internal_Wrapper($this); - } - return $this->wrapper->convert($name, $args); - } - // external Smarty methods ? - foreach(array('filter','register') as $external) { - if (method_exists("Smarty_Internal_{$external}",$name)) { - if (!isset($this->$external)) { - $class = "Smarty_Internal_{$external}"; - $this->$external = new $class($this); - } - return call_user_func_array(array($this->$external,$name), $args); - } - } - if (in_array($name,array('clearCompiledTemplate','compileAllTemplates','compileAllConfig','testInstall','getTags'))) { - if (!isset($this->utility)) { - $this->utility = new Smarty_Internal_Utility($this); - } - return call_user_func_array(array($this->utility,$name), $args); - } - // PHP4 call to constructor? - if (strtolower($name) == 'smarty') { - throw new SmartyException('Please use parent::__construct() to call parent constuctor'); - return false; - } - throw new SmartyException("Call of unknown function '$name'."); - } -} + public $escape_html = false; -/** - * Autoloader - */ -function smartyAutoload($class) -{ - $_class = strtolower($class); - if (substr($_class, 0, 16) === 'smarty_internal_' || $_class == 'smarty_security') { - include SMARTY_SYSPLUGINS_DIR . $_class . '.php'; - } -} + /** + * start time for execution time calculation + * + * @var int + */ + public $start_time = 0; -/** - * Smarty exception class - */ -Class SmartyException extends Exception { -} + /** + * required by the compiler for BC + * + * @var string + */ + public $_current_file = null; -/** - * Smarty compiler exception class - */ -Class SmartyCompilerException extends SmartyException { -} + /** + * internal flag to enable parser debugging + * + * @var bool + */ + public $_parserdebug = false; + + /** + * This object type (Smarty = 1, template = 2, data = 4) + * + * @var int + */ + public $_objType = 1; + + /** + * Debug object + * + * @var Smarty_Internal_Debug + */ + public $_debug = null; -?> + /** + * template directory + * + * @var array + */ + protected $template_dir = array('./templates/'); + + /** + * flags for normalized template directory entries + * + * @var array + */ + protected $_processedTemplateDir = array(); + + /** + * config directory + * + * @var array + */ + protected $config_dir = array('./configs/'); + + /** + * flags for normalized template directory entries + * + * @var array + */ + protected $_processedConfigDir = array(); + + /** + * compile directory + * + * @var string + */ + protected $compile_dir = './templates_c/'; + + /** + * plugins directory + * + * @var array + */ + protected $plugins_dir = array(); + + /** + * cache directory + * + * @var string + */ + protected $cache_dir = './cache/'; + + /** + * removed properties + * + * @var string[] + */ + protected $obsoleteProperties = array( + 'resource_caching', 'template_resource_caching', 'direct_access_security', + '_dir_perms', '_file_perms', 'plugin_search_order', + 'inheritance_merge_compiled_includes', 'resource_cache_mode', + ); + + /** + * List of private properties which will call getter/setter on a direct access + * + * @var string[] + */ + protected $accessMap = array( + 'template_dir' => 'TemplateDir', 'config_dir' => 'ConfigDir', + 'plugins_dir' => 'PluginsDir', 'compile_dir' => 'CompileDir', + 'cache_dir' => 'CacheDir', + ); + + /** + * PHP7 Compatibility mode + * @var bool + */ + private $isMutingUndefinedOrNullWarnings = false; + + /** + * Initialize new Smarty object + */ + public function __construct() + { + $this->_clearTemplateCache(); + parent::__construct(); + if (is_callable('mb_internal_encoding')) { + mb_internal_encoding(Smarty::$_CHARSET); + } + $this->start_time = microtime(true); + if (isset($_SERVER[ 'SCRIPT_NAME' ])) { + Smarty::$global_tpl_vars[ 'SCRIPT_NAME' ] = new Smarty_Variable($_SERVER[ 'SCRIPT_NAME' ]); + } + // Check if we're running on windows + Smarty::$_IS_WINDOWS = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; + // let PCRE (preg_*) treat strings as ISO-8859-1 if we're not dealing with UTF-8 + if (Smarty::$_CHARSET !== 'UTF-8') { + Smarty::$_UTF8_MODIFIER = ''; + } + } + + /** + * Check if a template resource exists + * + * @param string $resource_name template name + * + * @return bool status + * @throws \SmartyException + */ + public function templateExists($resource_name) + { + // create source object + $source = Smarty_Template_Source::load(null, $this, $resource_name); + return $source->exists; + } + + /** + * Loads security class and enables security + * + * @param string|Smarty_Security $security_class if a string is used, it must be class-name + * + * @return Smarty current Smarty instance for chaining + * @throws \SmartyException + */ + public function enableSecurity($security_class = null) + { + Smarty_Security::enableSecurity($this, $security_class); + return $this; + } + + /** + * Disable security + * + * @return Smarty current Smarty instance for chaining + */ + public function disableSecurity() + { + $this->security_policy = null; + return $this; + } + + /** + * Add template directory(s) + * + * @param string|array $template_dir directory(s) of template sources + * @param string $key of the array element to assign the template dir to + * @param bool $isConfig true for config_dir + * + * @return Smarty current Smarty instance for chaining + */ + public function addTemplateDir($template_dir, $key = null, $isConfig = false) + { + if ($isConfig) { + $processed = &$this->_processedConfigDir; + $dir = &$this->config_dir; + $this->_configDirNormalized = false; + } else { + $processed = &$this->_processedTemplateDir; + $dir = &$this->template_dir; + $this->_templateDirNormalized = false; + } + if (is_array($template_dir)) { + foreach ($template_dir as $k => $v) { + if (is_int($k)) { + // indexes are not merged but appended + $dir[] = $v; + } else { + // string indexes are overridden + $dir[ $k ] = $v; + unset($processed[ $key ]); + } + } + } else { + if ($key !== null) { + // override directory at specified index + $dir[ $key ] = $template_dir; + unset($processed[ $key ]); + } else { + // append new directory + $dir[] = $template_dir; + } + } + return $this; + } + + /** + * Get template directories + * + * @param mixed $index index of directory to get, null to get all + * @param bool $isConfig true for config_dir + * + * @return array|string list of template directories, or directory of $index + */ + public function getTemplateDir($index = null, $isConfig = false) + { + if ($isConfig) { + $dir = &$this->config_dir; + } else { + $dir = &$this->template_dir; + } + if ($isConfig ? !$this->_configDirNormalized : !$this->_templateDirNormalized) { + $this->_normalizeTemplateConfig($isConfig); + } + if ($index !== null) { + return isset($dir[ $index ]) ? $dir[ $index ] : null; + } + return $dir; + } + + /** + * Set template directory + * + * @param string|array $template_dir directory(s) of template sources + * @param bool $isConfig true for config_dir + * + * @return \Smarty current Smarty instance for chaining + */ + public function setTemplateDir($template_dir, $isConfig = false) + { + if ($isConfig) { + $this->config_dir = array(); + $this->_processedConfigDir = array(); + } else { + $this->template_dir = array(); + $this->_processedTemplateDir = array(); + } + $this->addTemplateDir($template_dir, null, $isConfig); + return $this; + } + + /** + * Add config directory(s) + * + * @param string|array $config_dir directory(s) of config sources + * @param mixed $key key of the array element to assign the config dir to + * + * @return Smarty current Smarty instance for chaining + */ + public function addConfigDir($config_dir, $key = null) + { + return $this->addTemplateDir($config_dir, $key, true); + } + + /** + * Get config directory + * + * @param mixed $index index of directory to get, null to get all + * + * @return array configuration directory + */ + public function getConfigDir($index = null) + { + return $this->getTemplateDir($index, true); + } + + /** + * Set config directory + * + * @param $config_dir + * + * @return Smarty current Smarty instance for chaining + */ + public function setConfigDir($config_dir) + { + return $this->setTemplateDir($config_dir, true); + } + + /** + * Adds directory of plugin files + * + * @param null|array|string $plugins_dir + * + * @return Smarty current Smarty instance for chaining + */ + public function addPluginsDir($plugins_dir) + { + if (empty($this->plugins_dir)) { + $this->plugins_dir[] = SMARTY_PLUGINS_DIR; + } + $this->plugins_dir = array_merge($this->plugins_dir, (array)$plugins_dir); + $this->_pluginsDirNormalized = false; + return $this; + } + + /** + * Get plugin directories + * + * @return array list of plugin directories + */ + public function getPluginsDir() + { + if (empty($this->plugins_dir)) { + $this->plugins_dir[] = SMARTY_PLUGINS_DIR; + $this->_pluginsDirNormalized = false; + } + if (!$this->_pluginsDirNormalized) { + if (!is_array($this->plugins_dir)) { + $this->plugins_dir = (array)$this->plugins_dir; + } + foreach ($this->plugins_dir as $k => $v) { + $this->plugins_dir[ $k ] = $this->_realpath(rtrim($v ?? '', '/\\') . DIRECTORY_SEPARATOR, true); + } + $this->_cache[ 'plugin_files' ] = array(); + $this->_pluginsDirNormalized = true; + } + return $this->plugins_dir; + } + + /** + * Set plugins directory + * + * @param string|array $plugins_dir directory(s) of plugins + * + * @return Smarty current Smarty instance for chaining + */ + public function setPluginsDir($plugins_dir) + { + $this->plugins_dir = (array)$plugins_dir; + $this->_pluginsDirNormalized = false; + return $this; + } + + /** + * Get compiled directory + * + * @return string path to compiled templates + */ + public function getCompileDir() + { + if (!$this->_compileDirNormalized) { + $this->_normalizeDir('compile_dir', $this->compile_dir); + $this->_compileDirNormalized = true; + } + return $this->compile_dir; + } + + /** + * + * @param string $compile_dir directory to store compiled templates in + * + * @return Smarty current Smarty instance for chaining + */ + public function setCompileDir($compile_dir) + { + $this->_normalizeDir('compile_dir', $compile_dir); + $this->_compileDirNormalized = true; + return $this; + } + + /** + * Get cache directory + * + * @return string path of cache directory + */ + public function getCacheDir() + { + if (!$this->_cacheDirNormalized) { + $this->_normalizeDir('cache_dir', $this->cache_dir); + $this->_cacheDirNormalized = true; + } + return $this->cache_dir; + } + + /** + * Set cache directory + * + * @param string $cache_dir directory to store cached templates in + * + * @return Smarty current Smarty instance for chaining + */ + public function setCacheDir($cache_dir) + { + $this->_normalizeDir('cache_dir', $cache_dir); + $this->_cacheDirNormalized = true; + return $this; + } + + /** + * creates a template object + * + * @param string $template the resource handle of the template file + * @param mixed $cache_id cache id to be used with this template + * @param mixed $compile_id compile id to be used with this template + * @param object $parent next higher level of Smarty variables + * @param boolean $do_clone flag is Smarty object shall be cloned + * + * @return \Smarty_Internal_Template template object + * @throws \SmartyException + */ + public function createTemplate($template, $cache_id = null, $compile_id = null, $parent = null, $do_clone = true) + { + if ($cache_id !== null && (is_object($cache_id) || is_array($cache_id))) { + $parent = $cache_id; + $cache_id = null; + } + if ($parent !== null && is_array($parent)) { + $data = $parent; + $parent = null; + } else { + $data = null; + } + if (!$this->_templateDirNormalized) { + $this->_normalizeTemplateConfig(false); + } + $_templateId = $this->_getTemplateId($template, $cache_id, $compile_id); + $tpl = null; + if ($this->caching && isset(Smarty_Internal_Template::$isCacheTplObj[ $_templateId ])) { + $tpl = $do_clone ? clone Smarty_Internal_Template::$isCacheTplObj[ $_templateId ] : + Smarty_Internal_Template::$isCacheTplObj[ $_templateId ]; + $tpl->inheritance = null; + $tpl->tpl_vars = $tpl->config_vars = array(); + } elseif (!$do_clone && isset(Smarty_Internal_Template::$tplObjCache[ $_templateId ])) { + $tpl = clone Smarty_Internal_Template::$tplObjCache[ $_templateId ]; + $tpl->inheritance = null; + $tpl->tpl_vars = $tpl->config_vars = array(); + } else { + /* @var Smarty_Internal_Template $tpl */ + $tpl = new $this->template_class($template, $this, null, $cache_id, $compile_id, null, null); + $tpl->templateId = $_templateId; + } + if ($do_clone) { + $tpl->smarty = clone $tpl->smarty; + } + $tpl->parent = $parent ? $parent : $this; + // fill data if present + if (!empty($data) && is_array($data)) { + // set up variable values + foreach ($data as $_key => $_val) { + $tpl->tpl_vars[ $_key ] = new Smarty_Variable($_val); + } + } + if ($this->debugging || $this->debugging_ctrl === 'URL') { + $tpl->smarty->_debug = new Smarty_Internal_Debug(); + // check URL debugging control + if (!$this->debugging && $this->debugging_ctrl === 'URL') { + $tpl->smarty->_debug->debugUrl($tpl->smarty); + } + } + return $tpl; + } + + /** + * Takes unknown classes and loads plugin files for them + * class name format: Smarty_PluginType_PluginName + * plugin filename format: plugintype.pluginname.php + * + * @param string $plugin_name class plugin name to load + * @param bool $check check if already loaded + * + * @return string |boolean filepath of loaded file or false + * @throws \SmartyException + */ + public function loadPlugin($plugin_name, $check = true) + { + return $this->ext->loadPlugin->loadPlugin($this, $plugin_name, $check); + } + + /** + * Get unique template id + * + * @param string $template_name + * @param null|mixed $cache_id + * @param null|mixed $compile_id + * @param null $caching + * @param \Smarty_Internal_Template $template + * + * @return string + * @throws \SmartyException + */ + public function _getTemplateId( + $template_name, + $cache_id = null, + $compile_id = null, + $caching = null, + Smarty_Internal_Template $template = null + ) { + $template_name = (strpos($template_name, ':') === false) ? "{$this->default_resource_type}:{$template_name}" : + $template_name; + $cache_id = $cache_id === null ? $this->cache_id : $cache_id; + $compile_id = $compile_id === null ? $this->compile_id : $compile_id; + $caching = (int)($caching === null ? $this->caching : $caching); + if ((isset($template) && strpos($template_name, ':.') !== false) || $this->allow_ambiguous_resources) { + $_templateId = + Smarty_Resource::getUniqueTemplateName((isset($template) ? $template : $this), $template_name) . + "#{$cache_id}#{$compile_id}#{$caching}"; + } else { + $_templateId = $this->_joined_template_dir . "#{$template_name}#{$cache_id}#{$compile_id}#{$caching}"; + } + if (isset($_templateId[ 150 ])) { + $_templateId = sha1($_templateId); + } + return $_templateId; + } + + /** + * Normalize path + * - remove /./ and /../ + * - make it absolute if required + * + * @param string $path file path + * @param bool $realpath if true - convert to absolute + * false - convert to relative + * null - keep as it is but + * remove /./ /../ + * + * @return string + */ + public function _realpath($path, $realpath = null) + { + $nds = array('/' => '\\', '\\' => '/'); + preg_match( + '%^(?(?:[[:alpha:]]:[\\\\/]|/|[\\\\]{2}[[:alpha:]]+|[[:print:]]{2,}:[/]{2}|[\\\\])?)(?(.*))$%u', + $path, + $parts + ); + $path = $parts[ 'path' ]; + if ($parts[ 'root' ] === '\\') { + $parts[ 'root' ] = substr(getcwd(), 0, 2) . $parts[ 'root' ]; + } else { + if ($realpath !== null && !$parts[ 'root' ]) { + $path = getcwd() . DIRECTORY_SEPARATOR . $path; + } + } + // normalize DIRECTORY_SEPARATOR + $path = str_replace($nds[ DIRECTORY_SEPARATOR ], DIRECTORY_SEPARATOR, $path); + $parts[ 'root' ] = str_replace($nds[ DIRECTORY_SEPARATOR ], DIRECTORY_SEPARATOR, $parts[ 'root' ]); + do { + $path = preg_replace( + array('#[\\\\/]{2}#', '#[\\\\/][.][\\\\/]#', '#[\\\\/]([^\\\\/.]+)[\\\\/][.][.][\\\\/]#'), + DIRECTORY_SEPARATOR, + $path, + -1, + $count + ); + } while ($count > 0); + return $realpath !== false ? $parts[ 'root' ] . $path : str_ireplace(getcwd(), '.', $parts[ 'root' ] . $path); + } + + /** + * Empty template objects cache + */ + public function _clearTemplateCache() + { + Smarty_Internal_Template::$isCacheTplObj = array(); + Smarty_Internal_Template::$tplObjCache = array(); + } + + /** + * @param boolean $use_sub_dirs + */ + public function setUseSubDirs($use_sub_dirs) + { + $this->use_sub_dirs = $use_sub_dirs; + } + + /** + * @param int $error_reporting + */ + public function setErrorReporting($error_reporting) + { + $this->error_reporting = $error_reporting; + } + + /** + * @param boolean $escape_html + */ + public function setEscapeHtml($escape_html) + { + $this->escape_html = $escape_html; + } + + /** + * Return auto_literal flag + * + * @return boolean + */ + public function getAutoLiteral() + { + return $this->auto_literal; + } + + /** + * Set auto_literal flag + * + * @param boolean $auto_literal + */ + public function setAutoLiteral($auto_literal = true) + { + $this->auto_literal = $auto_literal; + } + + /** + * @param boolean $force_compile + */ + public function setForceCompile($force_compile) + { + $this->force_compile = $force_compile; + } + + /** + * @param boolean $merge_compiled_includes + */ + public function setMergeCompiledIncludes($merge_compiled_includes) + { + $this->merge_compiled_includes = $merge_compiled_includes; + } + + /** + * Get left delimiter + * + * @return string + */ + public function getLeftDelimiter() + { + return $this->left_delimiter; + } + + /** + * Set left delimiter + * + * @param string $left_delimiter + */ + public function setLeftDelimiter($left_delimiter) + { + $this->left_delimiter = $left_delimiter; + } + + /** + * Get right delimiter + * + * @return string $right_delimiter + */ + public function getRightDelimiter() + { + return $this->right_delimiter; + } + + /** + * Set right delimiter + * + * @param string + */ + public function setRightDelimiter($right_delimiter) + { + $this->right_delimiter = $right_delimiter; + } + + /** + * @param boolean $debugging + */ + public function setDebugging($debugging) + { + $this->debugging = $debugging; + } + + /** + * @param boolean $config_overwrite + */ + public function setConfigOverwrite($config_overwrite) + { + $this->config_overwrite = $config_overwrite; + } + + /** + * @param boolean $config_booleanize + */ + public function setConfigBooleanize($config_booleanize) + { + $this->config_booleanize = $config_booleanize; + } + + /** + * @param boolean $config_read_hidden + */ + public function setConfigReadHidden($config_read_hidden) + { + $this->config_read_hidden = $config_read_hidden; + } + + /** + * @param boolean $compile_locking + */ + public function setCompileLocking($compile_locking) + { + $this->compile_locking = $compile_locking; + } + + /** + * @param string $default_resource_type + */ + public function setDefaultResourceType($default_resource_type) + { + $this->default_resource_type = $default_resource_type; + } + + /** + * @param string $caching_type + */ + public function setCachingType($caching_type) + { + $this->caching_type = $caching_type; + } + + /** + * Test install + * + * @param null $errors + */ + public function testInstall(&$errors = null) + { + Smarty_Internal_TestInstall::testInstall($this, $errors); + } + + /** + * Get Smarty object + * + * @return Smarty + */ + public function _getSmartyObj() + { + return $this; + } + + /** + * <> Generic getter. + * Calls the appropriate getter function. + * Issues an E_USER_NOTICE if no valid getter is found. + * + * @param string $name property name + * + * @return mixed + */ + public function __get($name) + { + if (isset($this->accessMap[ $name ])) { + $method = 'get' . $this->accessMap[ $name ]; + return $this->{$method}(); + } elseif (isset($this->_cache[ $name ])) { + return $this->_cache[ $name ]; + } elseif (in_array($name, $this->obsoleteProperties)) { + return null; + } else { + trigger_error('Undefined property: ' . get_class($this) . '::$' . $name, E_USER_NOTICE); + } + return null; + } + + /** + * <> Generic setter. + * Calls the appropriate setter function. + * Issues an E_USER_NOTICE if no valid setter is found. + * + * @param string $name property name + * @param mixed $value parameter passed to setter + * + */ + public function __set($name, $value) + { + if (isset($this->accessMap[ $name ])) { + $method = 'set' . $this->accessMap[ $name ]; + $this->{$method}($value); + } elseif (in_array($name, $this->obsoleteProperties)) { + return; + } elseif (is_object($value) && method_exists($value, $name)) { + $this->$name = $value; + } else { + trigger_error('Undefined property: ' . get_class($this) . '::$' . $name, E_USER_NOTICE); + } + } + + /** + * Normalize and set directory string + * + * @param string $dirName cache_dir or compile_dir + * @param string $dir filepath of folder + */ + private function _normalizeDir($dirName, $dir) + { + $this->{$dirName} = $this->_realpath(rtrim($dir ?? '', "/\\") . DIRECTORY_SEPARATOR, true); + } + + /** + * Normalize template_dir or config_dir + * + * @param bool $isConfig true for config_dir + */ + private function _normalizeTemplateConfig($isConfig) + { + if ($isConfig) { + $processed = &$this->_processedConfigDir; + $dir = &$this->config_dir; + } else { + $processed = &$this->_processedTemplateDir; + $dir = &$this->template_dir; + } + if (!is_array($dir)) { + $dir = (array)$dir; + } + foreach ($dir as $k => $v) { + if (!isset($processed[ $k ])) { + $dir[ $k ] = $v = $this->_realpath(rtrim($v ?? '', "/\\") . DIRECTORY_SEPARATOR, true); + $processed[ $k ] = true; + } + } + $isConfig ? $this->_configDirNormalized = true : $this->_templateDirNormalized = true; + $isConfig ? $this->_joined_config_dir = join('#', $this->config_dir) : + $this->_joined_template_dir = join('#', $this->template_dir); + } + + /** + * Activates PHP7 compatibility mode: + * - converts E_WARNINGS for "undefined array key" and "trying to read property of null" errors to E_NOTICE + * + * @void + */ + public function muteUndefinedOrNullWarnings(): void { + $this->isMutingUndefinedOrNullWarnings = true; + } + + /** + * Indicates if PHP7 compatibility mode is set. + * @bool + */ + public function isMutingUndefinedOrNullWarnings(): bool { + return $this->isMutingUndefinedOrNullWarnings; + } + +} diff --git a/WEB-INF/lib/smarty/bootstrap.php b/WEB-INF/lib/smarty/bootstrap.php new file mode 100644 index 000000000..a226ac04e --- /dev/null +++ b/WEB-INF/lib/smarty/bootstrap.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +/** + * Load and register Smarty Autoloader + */ +if (!class_exists('Smarty_Autoloader')) { + include __DIR__ . '/Autoloader.php'; +} +Smarty_Autoloader::register(true); diff --git a/WEB-INF/lib/smarty/debug.tpl b/WEB-INF/lib/smarty/debug.tpl index 058c5b204..4f82a5820 100644 --- a/WEB-INF/lib/smarty/debug.tpl +++ b/WEB-INF/lib/smarty/debug.tpl @@ -1,133 +1,175 @@ {capture name='_smarty_debug' assign=debug_output} - - - - Smarty Debug Console - - - - -

Smarty Debug Console - {if isset($template_name)}{$template_name|debug_print_var}{else}Total Time {$execution_time|string_format:"%.5f"}{/if}

- -{if !empty($template_data)} -

included templates & config files (load time in seconds)

- -
-{foreach $template_data as $template} - {$template.name} - - (compile {$template['compile_time']|string_format:"%.5f"}) (render {$template['render_time']|string_format:"%.5f"}) (cache {$template['cache_time']|string_format:"%.5f"}) - -
-{/foreach} -
-{/if} - -

assigned template variables

- -
\n"; + $html .= ''.$this->lSelAll.' / '.$this->lSelNone.''; $html .= "
"; $html .= "\n\t\n"; @@ -148,10 +145,10 @@ function toStringControl() { $html .= "
\n"; $str = "\n"; $html .= "\n\tmName\" id=\"$this->mId\""; + $html .= " name=\"$this->name\" id=\"$this->id\""; - if ($this->mSize!="") - $html .= " size=\"$this->mSize\""; + if ($this->size!="") + $html .= " size=\"$this->size\""; - if ($this->mStyle!="") - $html .= " style=\"$this->mStyle\""; + if ($this->style!="") + $html .= " style=\"$this->style\""; $html .= " maxlength=\"50\""; - if ($this->mOnChange!="") - $html .= " onchange=\"$this->mOnChange\""; - - if ($this->mOnClick!="") - $html .= " onclick=\"$this->mOnClick\""; + if ($this->on_change!="") + $html .= " onchange=\"$this->on_change\""; - if ($this->mOnFocus!="") - $html .= " onfocus=\"$this->mOnFocus\""; + if ($this->on_click!="") + $html .= " onclick=\"$this->on_click\""; $html .= " value=\"".htmlspecialchars($this->getValue())."\""; $html .= ">"; - if (APP_NAME) - $app_root = '/'.APP_NAME; + $dir_name = $app_root = ''; + if (defined('DIR_NAME')) + $dir_name = trim(constant('DIR_NAME'), '/'); + if (!empty($dir_name)) + $app_root = '/'.$dir_name; - $html .= " mName."');\">\n"; + $html .= " name."');\">\n"; } return $html; diff --git a/WEB-INF/lib/form/DefaultCellRenderer.class.php b/WEB-INF/lib/form/DefaultCellRenderer.class.php index 1e36f79b5..55f4bbca5 100644 --- a/WEB-INF/lib/form/DefaultCellRenderer.class.php +++ b/WEB-INF/lib/form/DefaultCellRenderer.class.php @@ -23,7 +23,7 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ class DefaultCellRenderer { diff --git a/WEB-INF/lib/form/FloatField.class.php b/WEB-INF/lib/form/FloatField.class.php index 9cd38bfd8..e6cbf1068 100644 --- a/WEB-INF/lib/form/FloatField.class.php +++ b/WEB-INF/lib/form/FloatField.class.php @@ -23,25 +23,26 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('form.TextField'); class FloatField extends TextField { - var $mDelimiter = '.'; var $mFFormat; - var $cClassName = 'FloatField'; - + function __construct($name) { - $this->mName = $name; + global $user; + + $this->class = 'FloatField'; + $this->name = $name; + $this->mDelimiter = $user->getDecimalMark(); } - function setLocalization($i18n) { - FormElement::setLocalization($i18n); + function localize() { global $user; - $this->mDelimiter = $user->decimal_mark; + $this->mDelimiter = $user->getDecimalMark(); } function setFormat($format) { @@ -54,19 +55,19 @@ function setValue($value) { $value = sprintf('%'.$this->mFFormat.'f', $value); $value = str_replace('.', $this->mDelimiter, $value); } - $this->mValue = $value; + $this->value = $value; } function setValueSafe($value) { // '.' to ',' , apply delimiter if (strlen($value) > 0) - $this->mValue = str_replace('.', $this->mDelimiter, $value); + $this->value = str_replace('.', $this->mDelimiter, $value); } function getValueSafe() { // ',' to '.' - if (strlen($this->mValue) > 0) - return str_replace($this->mDelimiter, '.', $this->mValue); + if (strlen($this->value) > 0) + return str_replace($this->mDelimiter, '.', $this->value); return null; } } diff --git a/WEB-INF/lib/form/Form.class.php b/WEB-INF/lib/form/Form.class.php index 0fcbd6208..cee608fe5 100644 --- a/WEB-INF/lib/form/Form.class.php +++ b/WEB-INF/lib/form/Form.class.php @@ -1,239 +1,156 @@ name = $name; + } - var $error; - var $debugFunction; - var $mElements = array(); - var $mRequest; - - function __construct($formName) { - $this->name = $formName; - } - - function setRequest(&$request) { - $this->mRequest = &$request; - } + function getElement($name) { + return $this->elements[$name]; + } - function &getElement($name) { - return $this->mElements[$name]; - } - - function &getElements() { - return $this->mElements; - } - - //// FORM element - // action - // method - GET, POST - // enctype - enctype="multipart/form-data" - // name - // onsubmit - // onreset - function getName() { return $this->name; } - - function isSubmit() { - if (!isset($this->mRequest)) return false; - $result = false; - foreach ($this->mElements as $el) { - if (strtolower(get_class($el))=="submit") { - $name = $el->getName(); - $value = $this->mRequest->getAttribute($name); - if($value) { - $result = true; - } - } - } - return $result; - } - - function OutputError($error,$scope="") - { - $this->error=(strcmp($scope,"") ? $scope.": ".$error : $error); - if(strcmp($function=$this->debugFunction,"") - && strcmp($this->error,"")) - $function($this->error); - return($this->error); - } - - //// INPUT element - // type = TEXT | PASSWORD | CHECKBOX | RADIO | SUBMIT | RESET | FILE | HIDDEN | IMAGE | BUTTON - // name - // value - // checked - for type radio and checkbox - // size - width pixels or chars - // maxlength - // src - for type image - // tabindex - support A, AREA, BUTTON, INPUT, OBJECT, SELECT, and TEXTAREA - // accesskey - support A, AREA, BUTTON, INPUT, LABEL, and LEGEND, and TEXTAREA - // onfocus - // onblur - // onselect - INPUT and TEXTAREA - // onchange - function addInput($arguments) { - if(strcmp(gettype($arguments),"array")) - $this->OutputError("arguments must be array","AddInput"); - - if(!isset($arguments["type"]) || !strcmp($arguments["type"],"")) - return($this->OutputError("Type not defined","AddInput")); - - if(!isset($arguments["name"]) || !strcmp($arguments["name"],"")) - return($this->OutputError("Name of element not defined","AddInput")); - - if (isset($this->mElements[$arguments["name"]])) - return($this->OutputError("it was specified '".$arguments["name"]."' name of an already defined input","AddInput")); - - switch($arguments["type"]) { - - case "textfield": - case "text": - import('form.TextField'); - $el = new TextField($arguments["name"]); - $el->setMaxLength(@$arguments["maxlength"]); - if (isset($arguments["aspassword"])) $el->setAsPassword($arguments["aspassword"]); - break; - - case "datefield": - import('form.DateField'); - $el = new DateField($arguments["name"]); - $el->setMaxLength("10"); - break; - - case "floatfield": - import('form.FloatField'); - $el = new FloatField($arguments["name"]); - if (isset($arguments["format"])) $el->setFormat($arguments["format"]); - break; - - case "textarea": - import('form.TextArea'); - $el = new TextArea($arguments["name"]); - $el->setColumns(@$arguments["cols"]); - $el->setRows(@$arguments["rows"]); - if (isset($arguments["maxlength"])) $el->setMaxLength($arguments["maxlength"]); - break; - - case "checkbox": - import('form.Checkbox'); - $el = new Checkbox($arguments["name"]); - if (@$arguments["checked"]) $el->setChecked(true); - $el->setData(@$arguments["data"]); - break; - + function getElements() { + return $this->elements; + } + + function getName() { return $this->name; } + + // addInput - adds an input object to the form. + function addInput($params) { + switch($params['type']) { + case 'text': + import('form.TextField'); + $el = new TextField($params['name']); + if (isset($params['class'])) $el->setCssClass($params['class']); + if (isset($params['maxlength'])) $el->setMaxLength($params['maxlength']); + if (isset($params['placeholder'])) $el->setPLaceholder($params['placeholder']); + break; + + case 'password': + import('form.PasswordField'); + $el = new PasswordField($params['name']); + if (isset($params['class'])) $el->setCssClass($params['class']); + if (isset($params['maxlength'])) $el->setMaxLength($params['maxlength']); + break; + + case 'datefield': + import('form.DateField'); + $el = new DateField($params['name']); + $el->setMaxLength('10'); + break; + + case 'floatfield': + import('form.FloatField'); + $el = new FloatField($params['name']); + if (isset($params['format'])) $el->setFormat($params['format']); + break; + + case 'textarea': + import('form.TextArea'); + $el = new TextArea($params['name']); + if (isset($params['maxlength'])) $el->setMaxLength($params['maxlength']); + break; + + case 'checkbox': + import('form.Checkbox'); + $el = new Checkbox($params['name']); + break; + + case 'hidden': + import('form.Hidden'); + $el = new Hidden($params['name']); + break; + + case 'submit': + import('form.Submit'); + $el = new Submit($params['name']); + break; + + case 'upload': + import('form.UploadFile'); + $el = new UploadFile($params['name']); + if (isset($params['maxsize'])) $el->setMaxSize($params['maxsize']); + break; + +// TODO: refactoring ongoing down from here. case "checkboxgroup": import('form.CheckboxGroup'); - $el = new CheckboxGroup($arguments["name"]); - if (isset($arguments["layout"])) $el->setLayout($arguments["layout"]); - if (isset($arguments["groupin"])) $el->setGroupIn($arguments["groupin"]); - if (isset($arguments["datakeys"])) $el->setDataKeys($arguments["datakeys"]); - $el->setData(@$arguments["data"]); + $el = new CheckboxGroup($params["name"]); + if (isset($params["layout"])) $el->setLayout($params["layout"]); + if (isset($params["groupin"])) $el->setGroupIn($params["groupin"]); + if (isset($params["datakeys"])) $el->setDataKeys($params["datakeys"]); + $el->setData(@$params["data"]); break; case "combobox": import('form.Combobox'); - $el = new Combobox($arguments["name"]); - $el->setData(@$arguments["data"]); - $el->setDataDefault(@$arguments["empty"]); - if (isset($arguments["datakeys"])) $el->setDataKeys($arguments["datakeys"]); + $el = new Combobox($params["name"]); + $el->setData(@$params["data"]); + $el->setDataDefault(@$params["empty"]); + if (isset($params['class'])) $el->setCssClass($params['class']); + if (isset($params["multiple"])) { + $el->setMultiple($params["multiple"]); + $el->name .= '[]'; // Add brackets to the end of name to get back an array on POST. + } + if (isset($params["datakeys"])) $el->setDataKeys($params["datakeys"]); break; - - case "hidden": - import('form.Hidden'); - $el = new Hidden($arguments["name"]); - break; - - case "submit": - import('form.Submit'); - $el = new Submit($arguments["name"]); + + case "multipleselectcombobox": + import('form.MultipleSelectCombobox'); + $multipleSelectComboboxName = $params["name"].'[]'; + $el = new MultipleSelectCombobox($multipleSelectComboboxName); + $el->setData(@$params["data"]); + $el->setDataDefault(@$params["empty"]); + if (isset($params['class'])) $el->setCssClass($params['class']); + if (isset($params["datakeys"])) $el->setDataKeys($params["datakeys"]); break; - + case "calendar": import('form.Calendar'); - $el = new Calendar($arguments["name"]); - $el->setHighlight(@$arguments["highlight"]); + $el = new Calendar($params["name"]); + $el->setHighlight(@$params["highlight"]); break; case "table": import('form.Table'); - $el = new Table($arguments["name"]); - $el->setData(@$arguments["data"]); - $el->setWidth(@$arguments["width"]); + $el = new Table($params["name"]); + $el->setData(@$params["data"]); + $el->setWidth(@$params["width"]); break; - - case "upload": - import('form.UploadFile'); - $el = new UploadFile($arguments["name"]); - if (isset($arguments["maxsize"])) $el->setMaxSize($arguments["maxsize"]); - break; - - default: - return($this->OutputError("Type not found for input element","AddInput")); } if ($el!=null) { $el->setFormName($this->name); - if (isset($arguments["id"])) $el->setId($arguments["id"]); - if (isset($GLOBALS["I18N"])) $el->setLocalization($GLOBALS["I18N"]); - if (isset($arguments["render"])) $el->setRenderable($arguments["render"]); - if (isset($arguments["enable"])) $el->setEnable($arguments["enable"]); + if (isset($params["id"])) $el->setId($params["id"]); + $el->localize(); + if (isset($params["enable"])) $el->setEnabled($params["enable"]); - if (isset($arguments["style"])) $el->setStyle($arguments["style"]); - if (isset($arguments["size"])) $el->setSize($arguments["size"]); + if (isset($params["style"])) $el->setStyle($params["style"]); + if (isset($params["size"])) $el->setSize($params["size"]); - if (isset($arguments["label"])) $el->setLabel($arguments["label"]); - if (isset($arguments["value"])) $el->setValue($arguments["value"]); + if (isset($params["label"])) $el->setLabel($params["label"]); + if (isset($params["value"])) $el->setValue($params["value"]); - if (isset($arguments["onchange"])) $el->setOnChange($arguments["onchange"]); - if (isset($arguments["onclick"])) $el->setOnClick($arguments["onclick"]); + if (isset($params["onchange"])) $el->setOnChange($params["onchange"]); + if (isset($params["onclick"])) $el->setOnClick($params["onclick"]); - $this->mElements[$arguments["name"]] = &$el; + $this->elements[$params["name"]] = &$el; } } function addInputElement(&$el) { if ($el && is_object($el)) { - if (!$el->getName()) - return($this->OutputError("no name in element","addInputElement")); - - if (isset($GLOBALS["I18N"])) $el->setLocalization($GLOBALS["I18N"]); + $el->localize(); $el->setFormName($this->name); - $this->mElements[$el->getName()] = &$el; + $this->elements[$el->name] = &$el; } } @@ -244,8 +161,8 @@ function toStringOpenTag() { $html .= ' method="post"'; // Add enctype for file upload forms. - foreach ($this->mElements as $elname=>$el) { - if (strtolower(get_class($this->mElements[$elname])) == 'uploadfile') { + foreach ($this->elements as $elname=>$el) { + if (strtolower(get_class($this->elements[$elname])) == 'uploadfile') { $html .= ' enctype="multipart/form-data"'; break; } @@ -257,9 +174,9 @@ function toStringOpenTag() { function toStringCloseTag() { $html = "\n"; - foreach ($this->mElements as $elname=>$el) { - if (strtolower(get_class($this->mElements[$elname]))=="hidden") { - $html .= $this->mElements[$elname]->toStringControl()."\n"; + foreach ($this->elements as $elname=>$el) { + if (strtolower(get_class($this->elements[$elname]))=="hidden") { + $html .= $this->elements[$elname]->getHtml()."\n"; } } $html .= ""; @@ -271,21 +188,21 @@ function toArray() { $vars['open'] = $this->toStringOpenTag(); $vars['close'] = $this->toStringCloseTag(); - foreach ($this->mElements as $elname=>$el) { - if (is_object($this->mElements[$elname])) - $vars[$elname] = $this->mElements[$elname]->toArray(); + foreach ($this->elements as $elname=>$el) { + if (is_object($this->elements[$elname])) + $vars[$elname] = $this->elements[$elname]->toArray(); } //print_r($vars); return $vars; } function getValueByElement($elname) { - return $this->mElements[$elname]->getValue(); + return $this->elements[$elname]->getValue(); } function setValueByElement($elname, $value) { - if (isset($this->mElements[$elname])) { - $this->mElements[$elname]->setValue($value); + if (isset($this->elements[$elname])) { + $this->elements[$elname]->setValue($value); } } } diff --git a/WEB-INF/lib/form/FormElement.class.php b/WEB-INF/lib/form/FormElement.class.php index e7ea9ccba..16b8d76d1 100644 --- a/WEB-INF/lib/form/FormElement.class.php +++ b/WEB-INF/lib/form/FormElement.class.php @@ -1,117 +1,79 @@ cClassName; } - - function setName($name) { $this->mName = $name; } - function getName() { return $this->mName; } - - function setFormName($name) { $this->mFormName = $name; } - function getFormName() { return $this->mFormName; } - - function setValue($value) { $this->mValue = $value;} - function getValue() { return $this->mValue; } - - function setValueSafe($value) { $this->mValue = $value;} - function getValueSafe() { return $this->mValue; } - - function setId($id) { $this->mId = $id; } - function getId() { return $this->mId; } - - function setSize($value) { $this->mSize = $value; } - function getSize() { return $this->mSize; } - - function setLabel($label) { $this->mLabel = $label; } - function getLabel() { return $this->mLabel; } - - function setMaxLength($value) { $this->mMaxLength = $value; } - function getMaxLength() { return $this->mMaxLength; } - - function setTabindex($value) { $this->mTabindex = $value; } - function getTabindex() { return $this->mTabindex; } - - function setAccesskey($value) { $this->mAccesskey = $value; } - function getAccesskey() { return $this->mAccesskey; } - - function setStyle($value) { $this->mStyle = $value; } - function getStyle() { return $this->mStyle; } - - function setRenderable($flag) { $this->mRenderable = $flag; } - function isRenderable() { return $this->mRenderable; } - - function setEnable($flag) { $this->mEnabled = $flag; } - function isEnable() { return $this->mEnabled; } - - function setOnChange($str) { $this->mOnChange = $str; } - function setOnClick($str) { $this->mOnClick = $str; } - function setOnSelect($str) { $this->mOnSelect = $str; } - - function setLocalization($i18n) { - $this->mI18n = $i18n; - } - - function toStringControl() { - return ""; - } - - function toStringLabel() { - return ""; - } - - function toArray() { - return array( - "label"=>$this->toStringLabel(), - "control"=>$this->toStringControl() - ); - } + var $id = ''; // control id + var $name; // control name + var $form_name = ''; // form name the control is in + var $value = ''; // value of the control + var $placeholder = ''; // placeholder + var $size = ''; // control size + var $max_length = ''; // max length of text in control + var $on_change = ''; // what happens when value of control changes + var $on_click = ''; // what happens when the control is clicked + var $label = ''; // optional label for control + var $style = ''; // control style + var $enabled = true; // whether the control is enabled + var $class = 'FormElement'; // php class name for the control + var $css_class = null; // css class name for the control + function __construct() { + } + + function getName() { return $this->name; } + function getClass() { return $this->class; } + function getCssClass() { return $this->css_class; } + function setCssClass($css_class) { $this->css_class = $css_class; } + + function setFormName($name) { $this->form_name = $name; } + function getFormName() { return $this->form_name; } + + function setValue($value) { $this->value = $value; } + function getValue() { return $this->value; } + + // Safe function variations are used to store/read values in/from user session for further reuse. + // They may convert data in derived classes to some standard form. For example, floats are stored + // with a dot delimiter (not comma), and dates are stored in DB_DATEFORMAT. + // This allows to reuse data in session even when user changes the deliminter or date format. + function setValueSafe($value) { $this->value = $value;} + function getValueSafe() { return $this->value; } + + function setId($id) { $this->id = $id; } + function getId() { return $this->id; } + + function setSize($value) { $this->size = $value; } + function getSize() { return $this->size; } + + function setLabel($label) { $this->label = $label; } + function getLabel() { return $this->label; } + + function setMaxLength($value) { $this->max_length = $value; } + function getMaxLength() { return $this->max_length; } + + function setStyle($value) { $this->style = $value; } + function getStyle() { return $this->style; } + + function setEnabled($flag) { $this->enabled = $flag; } + function isEnabled() { return $this->enabled; } + + function setOnChange($str) { $this->on_change = $str; } + function setOnClick($str) { $this->on_click = $str; } + function setPlaceholder($str) { $this->placeholder = $str; } + + function localize() {} // Localization occurs in derived classes and is dependent on control type. + // For example, in calendar control we need to localize day and month names. + + // getHtml returns HTML for the element. + function getHtml() { return ''; } + + // getLabelHtml returns HTML code for element label. + function getLabelHtml() { return ''; } + + function toArray() { + return array( + 'label'=>$this->getLabelHtml(), + 'control'=>$this->getHtml()); + } } diff --git a/WEB-INF/lib/form/Hidden.class.php b/WEB-INF/lib/form/Hidden.class.php index 84837467c..89e90c7eb 100644 --- a/WEB-INF/lib/form/Hidden.class.php +++ b/WEB-INF/lib/form/Hidden.class.php @@ -23,31 +23,23 @@ // | // +----------------------------------------------------------------------+ // | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm +// | https://www.anuko.com/time-tracker/credits.htm // +----------------------------------------------------------------------+ import('form.FormElement'); - + class Hidden extends FormElement { - var $mValue; - var $cClassName = "Hidden"; - function __construct($name,$value="") - { - $this->mName = $name; - $this->mValue = $value; - } + function __construct($name) { + $this->class = 'Hidden'; + $this->name = $name; + } + + function getHtml() { + if ($this->id == '') $this->id = $this->name; - function toStringControl() { - - if ($this->mId=="") $this->mId = $this->mName; - - $html = "\n\tmName\" id=\"$this->mId\""; - - $html .= " value=\"".$this->getValue()."\""; - $html .= ">"; - - return $html; - } + $html = "\n\tid\" name=\"$this->name\""; + $html.= ' value="'.$this->getValue().'">'; + return $html; + } } diff --git a/WEB-INF/lib/form/MultipleSelectCombobox.class.php b/WEB-INF/lib/form/MultipleSelectCombobox.class.php new file mode 100644 index 000000000..2c2cf0501 --- /dev/null +++ b/WEB-INF/lib/form/MultipleSelectCombobox.class.php @@ -0,0 +1,88 @@ +class = 'MultipleSelectCombobox'; + $this->css_class = 'dropdown-field'; + $this->name = $name; + } + + //function setMultiple($value) { $this->mMultiple = $value; } + //function isMultiple() { return $this->mMultiple; + + function setData($value) { $this->mOptions = $value; } + function getData() { return $this->mOptions; } + + function setDataDefault($value) { $this->mOptionsEmpty = $value; } + function getDataDefault() { return $this->mOptionsEmpty; } + + function setDataKeys($keys) { $this->mDataKeys = $keys; $this->mDataDeep = 2; } + function getDataKeys() { return $this->mDataKeys; } + + function getHtml() { + + if ($this->id=="") $this->id = $this->name; + + $html = "\n\tcss_class\""; + $html .= " name=\"$this->name\" id=\"$this->id\""; + + if ($this->size!="") + $html .= " size=\"$this->size\""; + + $html .= " multiple"; + + if ($this->on_change!="") + $html .= " onchange=\"$this->on_change\""; + + if ($this->style!="") + $html .= " style=\"$this->style\""; + + if (!$this->isEnabled()) + $html .= " disabled"; + + $html .= ">\n"; + if (is_array($this->mOptionsEmpty) && (count($this->mOptionsEmpty) > 0)) + foreach ($this->mOptionsEmpty as $key=>$value) { + $html .= "\n"; + } + if (is_array($this->mOptions) && (count($this->mOptions) > 0)) + $selectedProjects = is_array($this->value) ? $this->value : array(); + foreach ($this->mOptions as $key=>$value) { + + if ($this->mDataDeep>1) { + $key = $value[$this->mDataKeys[0]]; + $value = $value[$this->mDataKeys[1]]; + } + $html .= "
- {foreach $assigned_vars as $vars} - - - - {/foreach} -
${$vars@key|escape:'html'}{$vars|debug_print_var}
- -

assigned config file variables (outer template scope)

- - - {foreach $config_vars as $vars} - - - - {/foreach} - -
{$vars@key|escape:'html'}{$vars|debug_print_var}
- - + + + + Smarty Debug Console + + + + +

Smarty {Smarty::SMARTY_VERSION} Debug Console + - {if isset($template_name)}{$template_name|debug_print_var nofilter} {/if}{if !empty($template_data)}Total Time {$execution_time|string_format:"%.5f"}{/if}

+ + {if !empty($template_data)} +

included templates & config files (load time in seconds)

+
+ {foreach $template_data as $template} + {$template.name} +
   + (compile {$template['compile_time']|string_format:"%.5f"}) (render {$template['render_time']|string_format:"%.5f"}) (cache {$template['cache_time']|string_format:"%.5f"}) + +
+ {/foreach} +
+ {/if} + +

assigned template variables

+ + + {foreach $assigned_vars as $vars} + + + + + {/foreach} +
+

${$vars@key}

+ {if isset($vars['nocache'])}Nocache
{/if} + {if isset($vars['scope'])}Origin: {$vars['scope']|debug_print_var nofilter}{/if} +
+

Value

+ {$vars['value']|debug_print_var:10:80 nofilter} +
+ {if isset($vars['attributes'])} +

Attributes

+ {$vars['attributes']|debug_print_var nofilter} + {/if} +
+ +

assigned config file variables

+ + + {foreach $config_vars as $vars} + + + + + {/foreach} + +
+

#{$vars@key}#

+ {if isset($vars['scope'])}Origin: {$vars['scope']|debug_print_var nofilter}{/if} +
+ {$vars['value']|debug_print_var:10:80 nofilter} +
+ + {/capture} diff --git a/WEB-INF/lib/smarty/functions.php b/WEB-INF/lib/smarty/functions.php new file mode 100644 index 000000000..bac00e521 --- /dev/null +++ b/WEB-INF/lib/smarty/functions.php @@ -0,0 +1,51 @@ +allow_php_tag) { - throw new SmartyException("{php} is deprecated, set allow_php_tag = true to enable"); - } - eval($content); - return ''; -} - -?> \ No newline at end of file diff --git a/WEB-INF/lib/smarty/plugins/block.textformat.php b/WEB-INF/lib/smarty/plugins/block.textformat.php index 517fd62dd..fed090e4d 100644 --- a/WEB-INF/lib/smarty/plugins/block.textformat.php +++ b/WEB-INF/lib/smarty/plugins/block.textformat.php @@ -2,41 +2,51 @@ /** * Smarty plugin to format text blocks * - * @package Smarty + * @package Smarty * @subpackage PluginsBlock */ - /** * Smarty {textformat}{/textformat} block plugin - * - * Type: block function
- * Name: textformat
+ * Type: block function + * Name: textformat * Purpose: format text a certain way with preset styles - * or custom wrap/indent settings
- * - * @link http://smarty.php.net/manual/en/language.function.textformat.php {textformat} - * (Smarty online manual) - * @param array $params parameters - *
- * Params:   style: string (email)
- *            indent: integer (0)
- *            wrap: integer (80)
- *            wrap_char string ("\n")
- *            indent_char: string (" ")
- *            wrap_boundary: boolean (true)
- * 
- * @author Monte Ohrt - * @param string $content contents of the block - * @param object $template template object - * @param boolean &$repeat repeat flag + * or custom wrap/indent settings + * Params: + * + * - style - string (email) + * - indent - integer (0) + * - wrap - integer (80) + * - wrap_char - string ("\n") + * - indent_char - string (" ") + * - wrap_boundary - boolean (true) + * + * @link https://www.smarty.net/manual/en/language.function.textformat.php {textformat} + * (Smarty online manual) + * + * @param array $params parameters + * @param string $content contents of the block + * @param Smarty_Internal_Template $template template object + * @param boolean &$repeat repeat flag + * * @return string content re-formatted + * @author Monte Ohrt + * @throws \SmartyException */ -function smarty_block_textformat($params, $content, $template, &$repeat) +function smarty_block_textformat($params, $content, Smarty_Internal_Template $template, &$repeat) { if (is_null($content)) { return; - } - + } + if (Smarty::$_MBSTRING) { + $template->_checkPlugins( + array( + array( + 'function' => 'smarty_modifier_mb_wordwrap', + 'file' => SMARTY_PLUGINS_DIR . 'modifier.mb_wordwrap.php' + ) + ) + ); + } $style = null; $indent = 0; $indent_first = 0; @@ -45,7 +55,6 @@ function smarty_block_textformat($params, $content, $template, &$repeat) $wrap_char = "\n"; $wrap_cut = false; $assign = null; - foreach ($params as $_key => $_val) { switch ($_key) { case 'style': @@ -54,49 +63,59 @@ function smarty_block_textformat($params, $content, $template, &$repeat) case 'assign': $$_key = (string)$_val; break; - case 'indent': case 'indent_first': case 'wrap': $$_key = (int)$_val; break; - case 'wrap_cut': $$_key = (bool)$_val; break; - default: - trigger_error("textformat: unknown attribute '$_key'"); - } - } - - if ($style == 'email') { + trigger_error("textformat: unknown attribute '{$_key}'"); + } + } + if ($style === 'email') { $wrap = 72; - } + } // split into paragraphs - $_paragraphs = preg_split('![\r\n][\r\n]!', $content); - $_output = ''; - - for($_x = 0, $_y = count($_paragraphs); $_x < $_y; $_x++) { - if ($_paragraphs[$_x] == '') { + $_paragraphs = preg_split('![\r\n]{2}!', $content); + foreach ($_paragraphs as &$_paragraph) { + if (!$_paragraph) { continue; - } + } // convert mult. spaces & special chars to single space - $_paragraphs[$_x] = preg_replace(array('!\s+!', '!(^\s+)|(\s+$)!'), array(' ', ''), $_paragraphs[$_x]); + $_paragraph = + preg_replace( + array( + '!\s+!' . Smarty::$_UTF8_MODIFIER, + '!(^\s+)|(\s+$)!' . Smarty::$_UTF8_MODIFIER + ), + array( + ' ', + '' + ), + $_paragraph + ); // indent first line if ($indent_first > 0) { - $_paragraphs[$_x] = str_repeat($indent_char, $indent_first) . $_paragraphs[$_x]; - } + $_paragraph = str_repeat($indent_char, $indent_first) . $_paragraph; + } // wordwrap sentences - $_paragraphs[$_x] = wordwrap($_paragraphs[$_x], $wrap - $indent, $wrap_char, $wrap_cut); + if (Smarty::$_MBSTRING) { + $_paragraph = smarty_modifier_mb_wordwrap($_paragraph, $wrap - $indent, $wrap_char, $wrap_cut); + } else { + $_paragraph = wordwrap($_paragraph, $wrap - $indent, $wrap_char, $wrap_cut); + } // indent lines if ($indent > 0) { - $_paragraphs[$_x] = preg_replace('!^!m', str_repeat($indent_char, $indent), $_paragraphs[$_x]); - } - } + $_paragraph = preg_replace('!^!m', str_repeat($indent_char, $indent), $_paragraph); + } + } $_output = implode($wrap_char . $wrap_char, $_paragraphs); - - return $assign ? $template->assign($assign, $_output) : $_output; -} - -?> \ No newline at end of file + if ($assign) { + $template->assign($assign, $_output); + } else { + return $_output; + } +} diff --git a/WEB-INF/lib/smarty/plugins/function.counter.php b/WEB-INF/lib/smarty/plugins/function.counter.php index 7c50bd44b..54795459c 100644 --- a/WEB-INF/lib/smarty/plugins/function.counter.php +++ b/WEB-INF/lib/smarty/plugins/function.counter.php @@ -1,78 +1,62 @@ - * Name: counter
+ * Type: function + * Name: counter * Purpose: print out a counter value + * * @author Monte Ohrt - * @link http://smarty.php.net/manual/en/language.function.counter.php {counter} - * (Smarty online manual) - * @param array parameters - * @param Smarty - * @param object $template template object + * @link https://www.smarty.net/manual/en/language.function.counter.php {counter} + * (Smarty online manual) + * + * @param array $params parameters + * @param Smarty_Internal_Template $template template object + * * @return string|null */ function smarty_function_counter($params, $template) { static $counters = array(); - - $name = (isset($params['name'])) ? $params['name'] : 'default'; - if (!isset($counters[$name])) { - $counters[$name] = array( - 'start'=>1, - 'skip'=>1, - 'direction'=>'up', - 'count'=>1 - ); + $name = (isset($params[ 'name' ])) ? $params[ 'name' ] : 'default'; + if (!isset($counters[ $name ])) { + $counters[ $name ] = array('start' => 1, 'skip' => 1, 'direction' => 'up', 'count' => 1); } - $counter =& $counters[$name]; - - if (isset($params['start'])) { - $counter['start'] = $counter['count'] = (int)$params['start']; + $counter =& $counters[ $name ]; + if (isset($params[ 'start' ])) { + $counter[ 'start' ] = $counter[ 'count' ] = (int)$params[ 'start' ]; } - - if (!empty($params['assign'])) { - $counter['assign'] = $params['assign']; + if (!empty($params[ 'assign' ])) { + $counter[ 'assign' ] = $params[ 'assign' ]; } - - if (isset($counter['assign'])) { - $template->assign($counter['assign'], $counter['count']); + if (isset($counter[ 'assign' ])) { + $template->assign($counter[ 'assign' ], $counter[ 'count' ]); } - - if (isset($params['print'])) { - $print = (bool)$params['print']; + if (isset($params[ 'print' ])) { + $print = (bool)$params[ 'print' ]; } else { - $print = empty($counter['assign']); + $print = empty($counter[ 'assign' ]); } - if ($print) { - $retval = $counter['count']; + $retval = $counter[ 'count' ]; } else { $retval = null; } - - if (isset($params['skip'])) { - $counter['skip'] = $params['skip']; + if (isset($params[ 'skip' ])) { + $counter[ 'skip' ] = $params[ 'skip' ]; + } + if (isset($params[ 'direction' ])) { + $counter[ 'direction' ] = $params[ 'direction' ]; } - - if (isset($params['direction'])) { - $counter['direction'] = $params['direction']; + if ($counter[ 'direction' ] === 'down') { + $counter[ 'count' ] -= $counter[ 'skip' ]; + } else { + $counter[ 'count' ] += $counter[ 'skip' ]; } - - if ($counter['direction'] == "down") - $counter['count'] -= $counter['skip']; - else - $counter['count'] += $counter['skip']; - return $retval; - } - -?> \ No newline at end of file diff --git a/WEB-INF/lib/smarty/plugins/function.cycle.php b/WEB-INF/lib/smarty/plugins/function.cycle.php index 98e3e2878..793569991 100644 --- a/WEB-INF/lib/smarty/plugins/function.cycle.php +++ b/WEB-INF/lib/smarty/plugins/function.cycle.php @@ -2,105 +2,91 @@ /** * Smarty plugin * - * @package Smarty + * @package Smarty * @subpackage PluginsFunction */ - /** * Smarty {cycle} function plugin + * Type: function + * Name: cycle + * Date: May 3, 2002 + * Purpose: cycle through given values + * Params: * - * Type: function
- * Name: cycle
- * Date: May 3, 2002
- * Purpose: cycle through given values
- * Input: - * - name = name of cycle (optional) - * - values = comma separated list of values to cycle, - * or an array of values to cycle - * (this can be left out for subsequent calls) - * - reset = boolean - resets given var to true - * - print = boolean - print var or not. default is true - * - advance = boolean - whether or not to advance the cycle - * - delimiter = the value delimiter, default is "," - * - assign = boolean, assigns to template var instead of - * printed. + * - name - name of cycle (optional) + * - values - comma separated list of values to cycle, or an array of values to cycle + * (this can be left out for subsequent calls) + * - reset - boolean - resets given var to true + * - print - boolean - print var or not. default is true + * - advance - boolean - whether or not to advance the cycle + * - delimiter - the value delimiter, default is "," + * - assign - boolean, assigns to template var instead of printed. + * + * Examples: * - * Examples:
- *
  * {cycle values="#eeeeee,#d0d0d0d"}
  * {cycle name=row values="one,two,three" reset=true}
  * {cycle name=row}
- * 
- * @link http://smarty.php.net/manual/en/language.function.cycle.php {cycle} - * (Smarty online manual) - * @author Monte Ohrt - * @author credit to Mark Priatel - * @author credit to Gerard - * @author credit to Jason Sweat - * @version 1.3 - * @param array - * @param object $template template object + * + * @link https://www.smarty.net/manual/en/language.function.cycle.php {cycle} + * (Smarty online manual) + * @author Monte Ohrt + * @author credit to Mark Priatel + * @author credit to Gerard + * @author credit to Jason Sweat + * @version 1.3 + * + * @param array $params parameters + * @param Smarty_Internal_Template $template template object + * * @return string|null */ - function smarty_function_cycle($params, $template) { static $cycle_vars; - - $name = (empty($params['name'])) ? 'default' : $params['name']; - $print = (isset($params['print'])) ? (bool)$params['print'] : true; - $advance = (isset($params['advance'])) ? (bool)$params['advance'] : true; - $reset = (isset($params['reset'])) ? (bool)$params['reset'] : false; - - if (!in_array('values', array_keys($params))) { - if(!isset($cycle_vars[$name]['values'])) { - trigger_error("cycle: missing 'values' parameter"); + $name = (empty($params[ 'name' ])) ? 'default' : $params[ 'name' ]; + $print = (isset($params[ 'print' ])) ? (bool)$params[ 'print' ] : true; + $advance = (isset($params[ 'advance' ])) ? (bool)$params[ 'advance' ] : true; + $reset = (isset($params[ 'reset' ])) ? (bool)$params[ 'reset' ] : false; + if (!isset($params[ 'values' ])) { + if (!isset($cycle_vars[ $name ][ 'values' ])) { + trigger_error('cycle: missing \'values\' parameter'); return; } } else { - if(isset($cycle_vars[$name]['values']) - && $cycle_vars[$name]['values'] != $params['values'] ) { - $cycle_vars[$name]['index'] = 0; + if (isset($cycle_vars[ $name ][ 'values' ]) && $cycle_vars[ $name ][ 'values' ] !== $params[ 'values' ]) { + $cycle_vars[ $name ][ 'index' ] = 0; } - $cycle_vars[$name]['values'] = $params['values']; + $cycle_vars[ $name ][ 'values' ] = $params[ 'values' ]; } - - if (isset($params['delimiter'])) { - $cycle_vars[$name]['delimiter'] = $params['delimiter']; - } elseif (!isset($cycle_vars[$name]['delimiter'])) { - $cycle_vars[$name]['delimiter'] = ','; + if (isset($params[ 'delimiter' ])) { + $cycle_vars[ $name ][ 'delimiter' ] = $params[ 'delimiter' ]; + } elseif (!isset($cycle_vars[ $name ][ 'delimiter' ])) { + $cycle_vars[ $name ][ 'delimiter' ] = ','; } - - if(is_array($cycle_vars[$name]['values'])) { - $cycle_array = $cycle_vars[$name]['values']; + if (is_array($cycle_vars[ $name ][ 'values' ])) { + $cycle_array = $cycle_vars[ $name ][ 'values' ]; } else { - $cycle_array = explode($cycle_vars[$name]['delimiter'],$cycle_vars[$name]['values']); + $cycle_array = explode($cycle_vars[ $name ][ 'delimiter' ], $cycle_vars[ $name ][ 'values' ]); } - - if(!isset($cycle_vars[$name]['index']) || $reset ) { - $cycle_vars[$name]['index'] = 0; + if (!isset($cycle_vars[ $name ][ 'index' ]) || $reset) { + $cycle_vars[ $name ][ 'index' ] = 0; } - - if (isset($params['assign'])) { + if (isset($params[ 'assign' ])) { $print = false; - $template->assign($params['assign'], $cycle_array[$cycle_vars[$name]['index']]); + $template->assign($params[ 'assign' ], $cycle_array[ $cycle_vars[ $name ][ 'index' ] ]); } - - if($print) { - $retval = $cycle_array[$cycle_vars[$name]['index']]; + if ($print) { + $retval = $cycle_array[ $cycle_vars[ $name ][ 'index' ] ]; } else { $retval = null; } - - if($advance) { - if ( $cycle_vars[$name]['index'] >= count($cycle_array) -1 ) { - $cycle_vars[$name]['index'] = 0; + if ($advance) { + if ($cycle_vars[ $name ][ 'index' ] >= count($cycle_array) - 1) { + $cycle_vars[ $name ][ 'index' ] = 0; } else { - $cycle_vars[$name]['index']++; + $cycle_vars[ $name ][ 'index' ]++; } } - return $retval; } - -?> \ No newline at end of file diff --git a/WEB-INF/lib/smarty/plugins/function.fetch.php b/WEB-INF/lib/smarty/plugins/function.fetch.php index 2b09fb947..4a3e88196 100644 --- a/WEB-INF/lib/smarty/plugins/function.fetch.php +++ b/WEB-INF/lib/smarty/plugins/function.fetch.php @@ -2,215 +2,203 @@ /** * Smarty plugin * - * @package Smarty + * @package Smarty * @subpackage PluginsFunction */ - /** * Smarty {fetch} plugin - * - * Type: function
- * Name: fetch
+ * Type: function + * Name: fetch * Purpose: fetch file, web or ftp data and display results - * @link http://smarty.php.net/manual/en/language.function.fetch.php {fetch} - * (Smarty online manual) + * + * @link https://www.smarty.net/manual/en/language.function.fetch.php {fetch} + * (Smarty online manual) * @author Monte Ohrt - * @param array $params parameters - * @param object $template template object - * @return string|null if the assign parameter is passed, Smarty assigns the - * result to a template variable + * + * @param array $params parameters + * @param Smarty_Internal_Template $template template object + * + * @throws SmartyException + * @return string|null if the assign parameter is passed, Smarty assigns the result to a template variable */ function smarty_function_fetch($params, $template) { - if (empty($params['file'])) { - trigger_error("[plugin] fetch parameter 'file' cannot be empty",E_USER_NOTICE); + if (empty($params[ 'file' ])) { + trigger_error('[plugin] fetch parameter \'file\' cannot be empty', E_USER_NOTICE); return; } - - $content = ''; - if (isset($template->security_policy) && !preg_match('!^(http|ftp)://!i', $params['file'])) { - if(!$template->security_policy->isTrustedResourceDir($params['file'])) { - return; - } - - // fetch the file - if($fp = @fopen($params['file'],'r')) { - while(!feof($fp)) { - $content .= fgets ($fp,4096); + // strip file protocol + if (stripos($params[ 'file' ], 'file://') === 0) { + $params[ 'file' ] = substr($params[ 'file' ], 7); + } + $protocol = strpos($params[ 'file' ], '://'); + if ($protocol !== false) { + $protocol = strtolower(substr($params[ 'file' ], 0, $protocol)); + } + if (isset($template->smarty->security_policy)) { + if ($protocol) { + // remote resource (or php stream, …) + if (!$template->smarty->security_policy->isTrustedUri($params[ 'file' ])) { + return; } - fclose($fp); } else { - trigger_error('[plugin] fetch cannot read file \'' . $params['file'] . '\'',E_USER_NOTICE); - return; + // local file + if (!$template->smarty->security_policy->isTrustedResourceDir($params[ 'file' ])) { + return; + } } - } else { - // not a local file - if(preg_match('!^http://!i',$params['file'])) { - // http fetch - if($uri_parts = parse_url($params['file'])) { - // set defaults - $host = $server_name = $uri_parts['host']; - $timeout = 30; - $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*"; - $agent = "Smarty Template Engine ".$template->_version; - $referer = ""; - $uri = !empty($uri_parts['path']) ? $uri_parts['path'] : '/'; - $uri .= !empty($uri_parts['query']) ? '?' . $uri_parts['query'] : ''; - $_is_proxy = false; - if(empty($uri_parts['port'])) { - $port = 80; - } else { - $port = $uri_parts['port']; - } - if(!empty($uri_parts['user'])) { - $user = $uri_parts['user']; - } - if(!empty($uri_parts['pass'])) { - $pass = $uri_parts['pass']; - } - // loop through parameters, setup headers - foreach($params as $param_key => $param_value) { - switch($param_key) { - case "file": - case "assign": - case "assign_headers": - break; - case "user": - if(!empty($param_value)) { - $user = $param_value; - } - break; - case "pass": - if(!empty($param_value)) { - $pass = $param_value; - } - break; - case "accept": - if(!empty($param_value)) { - $accept = $param_value; - } - break; - case "header": - if(!empty($param_value)) { - if(!preg_match('![\w\d-]+: .+!',$param_value)) { - trigger_error("[plugin] invalid header format '".$param_value."'",E_USER_NOTICE); - return; - } else { - $extra_headers[] = $param_value; - } - } - break; - case "proxy_host": - if(!empty($param_value)) { - $proxy_host = $param_value; - } - break; - case "proxy_port": - if(!preg_match('!\D!', $param_value)) { - $proxy_port = (int) $param_value; - } else { - trigger_error("[plugin] invalid value for attribute '".$param_key."'",E_USER_NOTICE); + } + $content = ''; + if ($protocol === 'http') { + // http fetch + if ($uri_parts = parse_url($params[ 'file' ])) { + // set defaults + $host = $server_name = $uri_parts[ 'host' ]; + $timeout = 30; + $accept = 'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*'; + $agent = 'Smarty Template Engine ' . Smarty::SMARTY_VERSION; + $referer = ''; + $uri = !empty($uri_parts[ 'path' ]) ? $uri_parts[ 'path' ] : '/'; + $uri .= !empty($uri_parts[ 'query' ]) ? '?' . $uri_parts[ 'query' ] : ''; + $_is_proxy = false; + if (empty($uri_parts[ 'port' ])) { + $port = 80; + } else { + $port = $uri_parts[ 'port' ]; + } + if (!empty($uri_parts[ 'user' ])) { + $user = $uri_parts[ 'user' ]; + } + if (!empty($uri_parts[ 'pass' ])) { + $pass = $uri_parts[ 'pass' ]; + } + // loop through parameters, setup headers + foreach ($params as $param_key => $param_value) { + switch ($param_key) { + case 'file': + case 'assign': + case 'assign_headers': + break; + case 'user': + if (!empty($param_value)) { + $user = $param_value; + } + break; + case 'pass': + if (!empty($param_value)) { + $pass = $param_value; + } + break; + case 'accept': + if (!empty($param_value)) { + $accept = $param_value; + } + break; + case 'header': + if (!empty($param_value)) { + if (!preg_match('![\w\d-]+: .+!', $param_value)) { + trigger_error("[plugin] invalid header format '{$param_value}'", E_USER_NOTICE); return; - } - break; - case "agent": - if(!empty($param_value)) { - $agent = $param_value; - } - break; - case "referer": - if(!empty($param_value)) { - $referer = $param_value; - } - break; - case "timeout": - if(!preg_match('!\D!', $param_value)) { - $timeout = (int) $param_value; } else { - trigger_error("[plugin] invalid value for attribute '".$param_key."'",E_USER_NOTICE); - return; + $extra_headers[] = $param_value; } - break; - default: - trigger_error("[plugin] unrecognized attribute '".$param_key."'",E_USER_NOTICE); + } + break; + case 'proxy_host': + if (!empty($param_value)) { + $proxy_host = $param_value; + } + break; + case 'proxy_port': + if (!preg_match('!\D!', $param_value)) { + $proxy_port = (int)$param_value; + } else { + trigger_error("[plugin] invalid value for attribute '{$param_key }'", E_USER_NOTICE); return; - } + } + break; + case 'agent': + if (!empty($param_value)) { + $agent = $param_value; + } + break; + case 'referer': + if (!empty($param_value)) { + $referer = $param_value; + } + break; + case 'timeout': + if (!preg_match('!\D!', $param_value)) { + $timeout = (int)$param_value; + } else { + trigger_error("[plugin] invalid value for attribute '{$param_key}'", E_USER_NOTICE); + return; + } + break; + default: + trigger_error("[plugin] unrecognized attribute '{$param_key}'", E_USER_NOTICE); + return; } - if(!empty($proxy_host) && !empty($proxy_port)) { - $_is_proxy = true; - $fp = fsockopen($proxy_host,$proxy_port,$errno,$errstr,$timeout); + } + if (!empty($proxy_host) && !empty($proxy_port)) { + $_is_proxy = true; + $fp = fsockopen($proxy_host, $proxy_port, $errno, $errstr, $timeout); + } else { + $fp = fsockopen($server_name, $port, $errno, $errstr, $timeout); + } + if (!$fp) { + trigger_error("[plugin] unable to fetch: $errstr ($errno)", E_USER_NOTICE); + return; + } else { + if ($_is_proxy) { + fputs($fp, 'GET ' . $params[ 'file' ] . " HTTP/1.0\r\n"); } else { - $fp = fsockopen($server_name,$port,$errno,$errstr,$timeout); + fputs($fp, "GET $uri HTTP/1.0\r\n"); } - - if(!$fp) { - trigger_error("[plugin] unable to fetch: $errstr ($errno)",E_USER_NOTICE); - return; - } else { - if($_is_proxy) { - fputs($fp, 'GET ' . $params['file'] . " HTTP/1.0\r\n"); - } else { - fputs($fp, "GET $uri HTTP/1.0\r\n"); - } - if(!empty($host)) { - fputs($fp, "Host: $host\r\n"); - } - if(!empty($accept)) { - fputs($fp, "Accept: $accept\r\n"); - } - if(!empty($agent)) { - fputs($fp, "User-Agent: $agent\r\n"); - } - if(!empty($referer)) { - fputs($fp, "Referer: $referer\r\n"); - } - if(isset($extra_headers) && is_array($extra_headers)) { - foreach($extra_headers as $curr_header) { - fputs($fp, $curr_header."\r\n"); - } - } - if(!empty($user) && !empty($pass)) { - fputs($fp, "Authorization: BASIC ".base64_encode("$user:$pass")."\r\n"); - } - - fputs($fp, "\r\n"); - while(!feof($fp)) { - $content .= fgets($fp,4096); - } - fclose($fp); - $csplit = preg_split("!\r\n\r\n!",$content,2); - - $content = $csplit[1]; - - if(!empty($params['assign_headers'])) { - $template->assign($params['assign_headers'],preg_split("!\r\n!",$csplit[0])); + if (!empty($host)) { + fputs($fp, "Host: $host\r\n"); + } + if (!empty($accept)) { + fputs($fp, "Accept: $accept\r\n"); + } + if (!empty($agent)) { + fputs($fp, "User-Agent: $agent\r\n"); + } + if (!empty($referer)) { + fputs($fp, "Referer: $referer\r\n"); + } + if (isset($extra_headers) && is_array($extra_headers)) { + foreach ($extra_headers as $curr_header) { + fputs($fp, $curr_header . "\r\n"); } } - } else { - trigger_error("[plugin fetch] unable to parse URL, check syntax",E_USER_NOTICE); - return; - } - } else { - // ftp fetch - if($fp = @fopen($params['file'],'r')) { - while(!feof($fp)) { - $content .= fgets ($fp,4096); + if (!empty($user) && !empty($pass)) { + fputs($fp, 'Authorization: BASIC ' . base64_encode("$user:$pass") . "\r\n"); + } + fputs($fp, "\r\n"); + while (!feof($fp)) { + $content .= fgets($fp, 4096); } fclose($fp); - } else { - trigger_error('[plugin] fetch cannot read file \'' . $params['file'] .'\'',E_USER_NOTICE); - return; + $csplit = preg_split("!\r\n\r\n!", $content, 2); + $content = $csplit[ 1 ]; + if (!empty($params[ 'assign_headers' ])) { + $template->assign($params[ 'assign_headers' ], preg_split("!\r\n!", $csplit[ 0 ])); + } } + } else { + trigger_error("[plugin fetch] unable to parse URL, check syntax", E_USER_NOTICE); + return; + } + } else { + $content = @file_get_contents($params[ 'file' ]); + if ($content === false) { + throw new SmartyException("{fetch} cannot read resource '" . $params[ 'file' ] . "'"); } - } - - - if (!empty($params['assign'])) { - $template->assign($params['assign'],$content); + if (!empty($params[ 'assign' ])) { + $template->assign($params[ 'assign' ], $content); } else { return $content; } } - -?> \ No newline at end of file diff --git a/WEB-INF/lib/smarty/plugins/function.html_checkboxes.php b/WEB-INF/lib/smarty/plugins/function.html_checkboxes.php index 6a1a3ffdc..a8e7a07d8 100644 --- a/WEB-INF/lib/smarty/plugins/function.html_checkboxes.php +++ b/WEB-INF/lib/smarty/plugins/function.html_checkboxes.php @@ -2,142 +2,285 @@ /** * Smarty plugin * - * @package Smarty + * @package Smarty * @subpackage PluginsFunction */ - /** * Smarty {html_checkboxes} function plugin - * - * File: function.html_checkboxes.php
- * Type: function
- * Name: html_checkboxes
- * Date: 24.Feb.2003
- * Purpose: Prints out a list of checkbox input types
+ * File: function.html_checkboxes.php + * Type: function + * Name: html_checkboxes + * Date: 24.Feb.2003 + * Purpose: Prints out a list of checkbox input types * Examples: - *
+ *
  * {html_checkboxes values=$ids output=$names}
  * {html_checkboxes values=$ids name='box' separator='
' output=$names} * {html_checkboxes values=$ids checked=$checked separator='
' output=$names} - *
- * @link http://smarty.php.net/manual/en/language.function.html.checkboxes.php {html_checkboxes} - * (Smarty online manual) - * @author Christopher Kvarme - * @author credits to Monte Ohrt - * @version 1.0 - * @param array $params parameters - * Input:
- * - name (optional) - string default "checkbox" - * - values (required) - array - * - options (optional) - associative array - * - checked (optional) - array default not set - * - separator (optional) - ie
or   - * - output (optional) - the output next to each checkbox - * - assign (optional) - assign the output as an array to this variable - * @param object $template template object + * + * Params: + * + * - name (optional) - string default "checkbox" + * - values (required) - array + * - options (optional) - associative array + * - checked (optional) - array default not set + * - separator (optional) - ie
or   + * - output (optional) - the output next to each checkbox + * - assign (optional) - assign the output as an array to this variable + * - escape (optional) - escape the content (not value), defaults to true + * + * @link https://www.smarty.net/manual/en/language.function.html.checkboxes.php {html_checkboxes} + * (Smarty online manual) + * @author Christopher Kvarme + * @author credits to Monte Ohrt + * @version 1.0 + * + * @param array $params parameters + * @param Smarty_Internal_Template $template template object + * * @return string - * @uses smarty_function_escape_special_chars() + * @uses smarty_function_escape_special_chars() + * @throws \SmartyException */ -function smarty_function_html_checkboxes($params, $template) +function smarty_function_html_checkboxes($params, Smarty_Internal_Template $template) { - require_once(SMARTY_PLUGINS_DIR . 'shared.escape_special_chars.php'); - + $template->_checkPlugins( + array( + array( + 'function' => 'smarty_function_escape_special_chars', + 'file' => SMARTY_PLUGINS_DIR . 'shared.escape_special_chars.php' + ) + ) + ); $name = 'checkbox'; $values = null; $options = null; - $selected = null; + $selected = array(); $separator = ''; + $escape = true; $labels = true; + $label_ids = false; $output = null; - $extra = ''; - - foreach($params as $_key => $_val) { - switch($_key) { + foreach ($params as $_key => $_val) { + switch ($_key) { case 'name': case 'separator': - $$_key = $_val; + $$_key = (string)$_val; break; - + case 'escape': case 'labels': + case 'label_ids': $$_key = (bool)$_val; break; - case 'options': $$_key = (array)$_val; break; - case 'values': case 'output': $$_key = array_values((array)$_val); break; - case 'checked': case 'selected': - $selected = array_map('strval', array_values((array)$_val)); + if (is_array($_val)) { + $selected = array(); + foreach ($_val as $_sel) { + if (is_object($_sel)) { + if (method_exists($_sel, '__toString')) { + $_sel = smarty_function_escape_special_chars((string)$_sel->__toString()); + } else { + trigger_error( + 'html_checkboxes: selected attribute contains an object of class \'' . + get_class($_sel) . '\' without __toString() method', + E_USER_NOTICE + ); + continue; + } + } else { + $_sel = smarty_function_escape_special_chars((string)$_sel); + } + $selected[ $_sel ] = true; + } + } elseif (is_object($_val)) { + if (method_exists($_val, '__toString')) { + $selected = smarty_function_escape_special_chars((string)$_val->__toString()); + } else { + trigger_error( + 'html_checkboxes: selected attribute is an object of class \'' . get_class($_val) . + '\' without __toString() method', + E_USER_NOTICE + ); + } + } else { + $selected = smarty_function_escape_special_chars((string)$_val); + } break; - case 'checkboxes': - trigger_error('html_checkboxes: the use of the "checkboxes" attribute is deprecated, use "options" instead', E_USER_WARNING); + trigger_error( + 'html_checkboxes: the use of the "checkboxes" attribute is deprecated, use "options" instead', + E_USER_WARNING + ); $options = (array)$_val; break; - case 'assign': break; - + case 'strict': + break; + case 'disabled': + case 'readonly': + if (!empty($params[ 'strict' ])) { + if (!is_scalar($_val)) { + trigger_error( + "html_options: {$_key} attribute must be a scalar, only boolean true or string '{$_key}' will actually add the attribute", + E_USER_NOTICE + ); + } + if ($_val === true || $_val === $_key) { + $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_key) . '"'; + } + break; + } + // omit break; to fall through! + // no break default: - if(!is_array($_val)) { - $extra .= ' '.$_key.'="'.smarty_function_escape_special_chars($_val).'"'; + if (!is_array($_val)) { + $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_val) . '"'; } else { - trigger_error("html_checkboxes: extra attribute '$_key' cannot be an array", E_USER_NOTICE); + trigger_error("html_checkboxes: extra attribute '{$_key}' cannot be an array", E_USER_NOTICE); } break; } } - - if (!isset($options) && !isset($values)) - return ''; /* raise error here? */ - - settype($selected, 'array'); + if (!isset($options) && !isset($values)) { + return ''; + } /* raise error here? */ $_html_result = array(); - if (isset($options)) { - - foreach ($options as $_key=>$_val) - $_html_result[] = smarty_function_html_checkboxes_output($name, $_key, $_val, $selected, $extra, $separator, $labels); - - + foreach ($options as $_key => $_val) { + $_html_result[] = + smarty_function_html_checkboxes_output( + $name, + $_key, + $_val, + $selected, + $extra, + $separator, + $labels, + $label_ids, + $escape + ); + } } else { - foreach ($values as $_i=>$_key) { - $_val = isset($output[$_i]) ? $output[$_i] : ''; - $_html_result[] = smarty_function_html_checkboxes_output($name, $_key, $_val, $selected, $extra, $separator, $labels); + foreach ($values as $_i => $_key) { + $_val = isset($output[ $_i ]) ? $output[ $_i ] : ''; + $_html_result[] = + smarty_function_html_checkboxes_output( + $name, + $_key, + $_val, + $selected, + $extra, + $separator, + $labels, + $label_ids, + $escape + ); } - } - - if(!empty($params['assign'])) { - $template->assign($params['assign'], $_html_result); + if (!empty($params[ 'assign' ])) { + $template->assign($params[ 'assign' ], $_html_result); } else { - return implode("\n",$_html_result); + return implode("\n", $_html_result); } - } -function smarty_function_html_checkboxes_output($name, $value, $output, $selected, $extra, $separator, $labels) { +/** + * @param $name + * @param $value + * @param $output + * @param $selected + * @param $extra + * @param $separator + * @param $labels + * @param $label_ids + * @param bool $escape + * + * @return string + */ +function smarty_function_html_checkboxes_output( + $name, + $value, + $output, + $selected, + $extra, + $separator, + $labels, + $label_ids, + $escape = true +) { $_output = ''; - if ($labels) $_output .= '
+ + + + + {foreach $active_clients as $client} + + + + {/foreach}
{$i18n.label.person_name}{$i18n.label.address}
{$client.name|escape}{$client.address|escape}
+{/if} diff --git a/WEB-INF/templates/display_options.tpl b/WEB-INF/templates/display_options.tpl new file mode 100644 index 000000000..0d0418af9 --- /dev/null +++ b/WEB-INF/templates/display_options.tpl @@ -0,0 +1,73 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.displayOptionsForm.open} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{$i18n.title.time}
{$forms.displayOptionsForm.time_note_on_separate_row.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.displayOptionsForm.time_not_complete_days.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.displayOptionsForm.record_custom_fields.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$i18n.title.reports}
{$forms.displayOptionsForm.report_note_on_separate_row.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.displayOptionsForm.report_inactive_projects.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.displayOptionsForm.report_cost_per_hour.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$i18n.form.display_options.custom_css}
{$forms.displayOptionsForm.custom_css.control}
{$i18n.label.what_is_it}
{$i18n.form.display_options.custom_translation}
{$forms.displayOptionsForm.custom_translation.control}
{$i18n.label.what_is_it}
+
{$forms.displayOptionsForm.btn_save.control}
+{$forms.displayOptionsForm.close} diff --git a/WEB-INF/templates/entity_files.tpl b/WEB-INF/templates/entity_files.tpl new file mode 100644 index 000000000..07a46443d --- /dev/null +++ b/WEB-INF/templates/entity_files.tpl @@ -0,0 +1,47 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{if $files} + + + + + + + + {foreach $files as $file} + + + + {if $can_edit} + + + {/if} + + {/foreach} +
{$i18n.label.thing_name}{$i18n.label.description}
{$file.name|escape}{$file.description|escape}{$i18n.label.edit}{$i18n.label.delete}
+{/if} + +{if $can_edit} +
+{$forms.fileUploadForm.open} + + + + + + + + + + + + + + +
{$forms.fileUploadForm.newfile.control}
{$forms.fileUploadForm.description.control}
{$forms.fileUploadForm.btn_submit.control}
+{$forms.fileUploadForm.close} +{/if} + + + diff --git a/WEB-INF/templates/expense_delete.tpl b/WEB-INF/templates/expense_delete.tpl index 8b4e642fb..a9ea04d10 100644 --- a/WEB-INF/templates/expense_delete.tpl +++ b/WEB-INF/templates/expense_delete.tpl @@ -1,39 +1,28 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.expenseItemForm.open} - - - -
- +
{if $user->isPluginEnabled('cl')} - + {/if} - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - +{if $show_project} + {/if} - - + + - + {if $user->isPluginEnabled('cl')} - + {/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - +{if $show_project} + {/if} - - - -
{$i18n.label.client}{$i18n.label.client}{$i18n.label.project}{$i18n.label.project}{$i18n.label.item}{$i18n.label.cost}{$i18n.label.item}{$i18n.label.cost}
{$expense_item.client_name|escape}{$expense_item.client_name|escape}{$expense_item.project_name|escape}{$expense_item.project_name|escape}{$expense_item.name|escape}{$expense_item.cost}
- - - - - - + + -
 
{$forms.expenseItemForm.delete_button.control}  {$forms.expenseItemForm.cancel_button.control}{$expense_item.name|escape}{$expense_item.cost}
-
+
{$forms.expenseItemForm.delete_button.control} {$forms.expenseItemForm.cancel_button.control}
{$forms.expenseItemForm.close} diff --git a/WEB-INF/templates/expense_edit.tpl b/WEB-INF/templates/expense_edit.tpl index 00b1ae539..fc5cef8cc 100644 --- a/WEB-INF/templates/expense_edit.tpl +++ b/WEB-INF/templates/expense_edit.tpl @@ -1,3 +1,6 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.expenseItemForm.open} - - - + + + + + + + + + + + + +{if ($user->can('manage_invoices') && $user->isPluginEnabled('ps'))} + + + + +{/if} + + + + +
- +
- - -
- {if $user->isPluginEnabled('cl')} - - - - + + + + + + {/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - +{if $show_project} + + + + + + {/if} {if $predefined_expenses} - - - - - - - - + + + + + + + + + + + + {/if} - - - - - - - - - - - - - - - - - - - -
{$i18n.label.client} {if $user->isPluginEnabled('cm')}(*){/if}:{$forms.expenseItemForm.client.control}
{$forms.expenseItemForm.client.control}
{$i18n.label.project} (*):{$forms.expenseItemForm.project.control}
{$forms.expenseItemForm.project.control}
{$i18n.label.expense}:{$forms.expenseItemForm.predefined_expense.control}
{$i18n.label.quantity}:{$forms.expenseItemForm.quantity.control}
{$forms.expenseItemForm.predefined_expense.control}
{$forms.expenseItemForm.quantity.control}
{$i18n.label.comment}:{$forms.expenseItemForm.item_name.control}
{$i18n.label.cost}:{$forms.expenseItemForm.cost.control} {$user->currency|escape}
{$i18n.label.date}:{$forms.expenseItemForm.date.control}
 
{$forms.expenseItemForm.btn_save.control} {$forms.expenseItemForm.btn_copy.control} {$forms.expenseItemForm.btn_delete.control}
-
-
{$forms.expenseItemForm.item_name.control}
{$forms.expenseItemForm.cost.control} {$user->getCurrency()|escape}
{$forms.expenseItemForm.date.control}
+
{$forms.expenseItemForm.btn_save.control} {$forms.expenseItemForm.btn_copy.control} {$forms.expenseItemForm.btn_delete.control}
{$forms.expenseItemForm.close} diff --git a/WEB-INF/templates/expenses.tpl b/WEB-INF/templates/expenses.tpl index d21050799..859ae3506 100644 --- a/WEB-INF/templates/expenses.tpl +++ b/WEB-INF/templates/expenses.tpl @@ -1,3 +1,6 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.expensesForm.open} - +
{$forms.expensesForm.date.control}
+
+ +{if isset($user_dropdown)} + - - + + + + -
{$forms.expensesForm.date.control}
- -{if $on_behalf_control} - - - - + + + + {/if} {if $user->isPluginEnabled('cl')} - - - - + + + + + + {/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - +{if $show_project} + + + + + + {/if} {if $predefined_expenses} - - - - - - - - - + + + + + + + + + + + + {/if} - - - - - - - - -
{$i18n.label.user}:{$forms.expensesForm.onBehalfUser.control}
{$forms.expensesForm.user.control}
{$i18n.label.client}{if $user->isPluginEnabled('cm')} (*){/if}:{$forms.expensesForm.client.control}
{$forms.expensesForm.client.control}
{$i18n.label.project} (*):{$forms.expensesForm.project.control}
{$forms.expensesForm.project.control}
{$i18n.label.expense}:{$forms.expensesForm.predefined_expense.control}
{$i18n.label.quantity}:{$forms.expensesForm.quantity.control}
{$forms.expensesForm.predefined_expense.control}
{$forms.expensesForm.quantity.control}
{$i18n.label.comment} (*):{$forms.expensesForm.item_name.control}
{$i18n.label.cost} (*):{$forms.expensesForm.cost.control} {$user->currency|escape}
-
- - -
{$forms.expensesForm.date.control}
-
{$forms.expensesForm.item_name.control}
- - + + + + + + + +{if $show_files} + - + + + + +{/if} + +
{$forms.expensesForm.cost.control}
{$forms.expensesForm.btn_submit.control}{$forms.expensesForm.newfile.control}
{$forms.expensesForm.btn_submit.control}
+{$forms.expensesForm.close} - - - -
{if $expense_items} - - +
+
+ {if $user->isPluginEnabled('cl')} - + {/if} - {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + {if $show_project} + {/if} - - - - + + + {if $show_files} + + {/if} + + + {foreach $expense_items as $item} - + {if $user->isPluginEnabled('cl')} - + + {/if} + {if $show_project} + {/if} - {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + + + {if $show_files} + {if $item.has_files} + + {else} + + {/if} {/if} - - - - + + + {/foreach} -
{$i18n.label.client}{$i18n.label.client}{$i18n.label.project}{$i18n.label.project}{$i18n.label.item}{$i18n.label.cost}{$i18n.label.edit}
{$i18n.label.item}{$i18n.label.cost}
{$item.client|escape}{$item.client|escape}{$item.project|escape}{$item.project|escape}{$item.item|escape}{$item.cost}{$i18n.label.files}{$i18n.label.files}{$item.item|escape}{$item.cost}{if $item.invoice_id} {else}{$i18n.label.edit}{/if}
+ {if $item.approved || $item.invoice_id} +   + {else} + {$i18n.label.edit} + {/if} + + {if $item.approved || $item.invoice_id} +   + {else} + {$i18n.label.delete} + {/if} +
- - - - -
{$i18n.label.day_total}: {$user->currency|escape} {$day_total}
-{/if} -
-{$forms.expensesForm.close} + +
{$i18n.label.day_total}: {$user->getCurrency()|escape} {$day_total}
+{/if} + diff --git a/WEB-INF/templates/export.tpl b/WEB-INF/templates/export.tpl index 1c9866edc..171aa9b1b 100644 --- a/WEB-INF/templates/export.tpl +++ b/WEB-INF/templates/export.tpl @@ -1,23 +1,16 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +
{$i18n.form.export.hint}
+ {$forms.exportForm.open} - +
+ - + + +
-{if $user->isManager()} - - - - - - - - - - - - -
{$i18n.form.export.hint}
 
{$i18n.form.export.compression}:{$forms.exportForm.compression.control}
{$forms.exportForm.btn_submit.control}
-{/if} -
{$forms.exportForm.compression.control}
+
{$forms.exportForm.btn_submit.control}
{$forms.exportForm.close} diff --git a/WEB-INF/templates/file_delete.tpl b/WEB-INF/templates/file_delete.tpl new file mode 100644 index 000000000..80abe01ce --- /dev/null +++ b/WEB-INF/templates/file_delete.tpl @@ -0,0 +1,7 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.fileDeleteForm.open} +
{$file_to_delete|escape}
+
{$forms.fileDeleteForm.btn_delete.control} {$forms.fileDeleteForm.btn_cancel.control}
+{$forms.fileDeleteForm.close} diff --git a/WEB-INF/templates/mobile/access_denied.tpl b/WEB-INF/templates/file_download.tpl similarity index 100% rename from WEB-INF/templates/mobile/access_denied.tpl rename to WEB-INF/templates/file_download.tpl diff --git a/WEB-INF/templates/file_edit.tpl b/WEB-INF/templates/file_edit.tpl new file mode 100644 index 000000000..66f72cc3d --- /dev/null +++ b/WEB-INF/templates/file_edit.tpl @@ -0,0 +1,22 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.fileForm.open} + + + + + + + + + + + + + + + +
{$forms.fileForm.file_name.control}
{$forms.fileForm.description.control}
{$i18n.label.required_fields}
+
{$forms.fileForm.btn_save.control}
+{$forms.fileForm.close} diff --git a/WEB-INF/templates/footer.tpl b/WEB-INF/templates/footer.tpl index 83e4d7473..8844f56e8 100644 --- a/WEB-INF/templates/footer.tpl +++ b/WEB-INF/templates/footer.tpl @@ -1,28 +1,7 @@ - - - - -

 

+ {* page-content *} - - - - -
{$i18n.footer.contribute_msg}
-
- - - - -
 Anuko Time Tracker 1.10.38.3580 | Copyright © Anuko | - {$i18n.footer.credits} | - {$i18n.footer.license} | - {$i18n.footer.improve} -
-
+ + - - - diff --git a/WEB-INF/templates/group_add.tpl b/WEB-INF/templates/group_add.tpl new file mode 100644 index 000000000..cc4231ebf --- /dev/null +++ b/WEB-INF/templates/group_add.tpl @@ -0,0 +1,22 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.groupForm.open} + + + + + + + + + + + + + + + +
{$forms.groupForm.group_name.control}
{$forms.groupForm.description.control}
{$i18n.label.required_fields}
+
{$forms.groupForm.btn_add.control}
+{$forms.groupForm.close} diff --git a/WEB-INF/templates/group_advanced_edit.tpl b/WEB-INF/templates/group_advanced_edit.tpl new file mode 100644 index 000000000..82eac702c --- /dev/null +++ b/WEB-INF/templates/group_advanced_edit.tpl @@ -0,0 +1,58 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.groupAdvancedForm.open} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{$forms.groupAdvancedForm.group_name.control}
{$forms.groupAdvancedForm.description.control}
{$forms.groupAdvancedForm.bcc_email.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.groupAdvancedForm.allow_ip.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.groupAdvancedForm.password_complexity.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.groupAdvancedForm.2fa.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$i18n.label.required_fields}
+
{$forms.groupAdvancedForm.btn_save.control}
+{$forms.groupAdvancedForm.close} diff --git a/WEB-INF/templates/group_delete.tpl b/WEB-INF/templates/group_delete.tpl new file mode 100644 index 000000000..5a02f29af --- /dev/null +++ b/WEB-INF/templates/group_delete.tpl @@ -0,0 +1,8 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.groupForm.open} +
{$i18n.form.group_delete.hint}
+
{$group_to_delete|escape}
+
{$forms.groupForm.btn_delete.control} {$forms.groupForm.btn_cancel.control}
+{$forms.groupForm.close} diff --git a/WEB-INF/templates/group_edit.tpl b/WEB-INF/templates/group_edit.tpl new file mode 100644 index 000000000..909b485e6 --- /dev/null +++ b/WEB-INF/templates/group_edit.tpl @@ -0,0 +1,183 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + + + + +{$forms.groupForm.open} +{include file="datetime_format_preview.tpl"} + +{if $user->can('manage_subgroups')} + {if isset($group_dropdown) && $group_dropdown} + + + + + + + {/if} + + + + + + +{/if} + + + + + + +{if $user->can('manage_roles')} + + + + + + +{/if} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{if $user->can('manage_advanced_settings')} + + + + + + +{/if} +
{$forms.groupForm.group.control}
{$i18n.label.subgroups}:
{$i18n.label.subgroups}:{$i18n.label.configure}
{$forms.groupForm.currency.control}
{$i18n.label.roles}:
{$i18n.label.roles}:{$i18n.label.configure}
{$forms.groupForm.lang.control}
{$forms.groupForm.decimal_mark.control}  
{$forms.groupForm.date_format.control}  
{$forms.groupForm.time_format.control}  
{$forms.groupForm.start_week.control}
{$i18n.form.group_edit.display_options}:
{$i18n.form.group_edit.display_options}:{$i18n.label.configure}
{$forms.groupForm.holidays.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.groupForm.tracking_mode.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.groupForm.record_type.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.groupForm.punch_mode.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.groupForm.one_uncompleted.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.groupForm.allow_overlap.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.groupForm.future_entries.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.groupForm.uncompleted_indicators.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.groupForm.confirm_save.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$i18n.form.group_edit.advanced_settings}:
{$i18n.form.group_edit.advanced_settings}:{$i18n.label.configure}
+
{$forms.groupForm.btn_save.control} {$forms.groupForm.btn_delete.control}
+{$forms.groupForm.close} + + diff --git a/WEB-INF/templates/groups.tpl b/WEB-INF/templates/groups.tpl new file mode 100644 index 000000000..b5ed9a7b2 --- /dev/null +++ b/WEB-INF/templates/groups.tpl @@ -0,0 +1,45 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + + + +{$forms.subgroupsForm.open} +{if isset($group_dropdown) && $group_dropdown} + + + + + + + +
{$forms.subgroupsForm.group.control}
+{/if} + +
+ +{if $subgroups} + + + + + + + + {foreach $subgroups as $subgroup} + + + + + + + {/foreach} +
{$i18n.label.thing_name}{{$i18n.label.description}}
{$subgroup.name|escape}{$subgroup.description|escape}{$i18n.label.edit}{$i18n.label.delete}
+{/if} +{$forms.subgroupsForm.close} +
+
+ +
+
diff --git a/WEB-INF/templates/header.tpl b/WEB-INF/templates/header.tpl index 4e4a41464..af6151acb 100644 --- a/WEB-INF/templates/header.tpl +++ b/WEB-INF/templates/header.tpl @@ -1,13 +1,18 @@ - + + - + + - -{if $i18n.language.rtl} - + +{if (isset($i18n.language.rtl) && $i18n.language.rtl)} + {/if} - Time Tracker{if $title} - {$title}{/if} +{if $user->getCustomCss()} + +{/if} + Time Tracker{if isset($title) && $title} - {$title}{/if} - - -{assign var="tab_width" value="700"} + - - - + + + {if $show_paid_column} + + {/if} + + + + + {if $show_paid_column} + + {/if} + + {/if} + + + + {if $show_paid_column} + + {/if} + +
- - - - + - -
+ Time Tracker {else} - + Anuko Time Tracker {/if} - - - - -
- - - -{if $user->custom_logo} - -{else} - -{/if} - -
Time TrackerAnuko Time Tracker
-
-
- + + +{* top menu for small screens *} + +{* end of top menu for small screens *} -{if $authenticated} - {if $user->isAdmin()} - - - - - -
  - {$i18n.menu.logout} · - {$i18n.menu.forum} · - {$i18n.menu.help} -
- +{* top menu for large screens *} +{if (isset($authenticated) && $authenticated)} + {if $user->can('administer_site')} + {* top menu for admin *} + + {* end of top menu for admin *} - - - - - -
  - {$i18n.menu.teams} · - {$i18n.menu.options} -
- + {* sub menu for admin *} + + {* end of sub menu for admin *} {else} - - - - - -
  - {$i18n.menu.logout} · - {$i18n.menu.profile} · - {$i18n.menu.forum} · - {$i18n.menu.help} -
- + {* top menu for authorized user *} +
+ + + + {if $user->exists() && $user->can('manage_own_settings')} + + {/if} + {if $user->can('manage_basic_settings')} + + {/if} + {if $user->can('manage_features')} + + {/if} + + + +
{$i18n.menu.logout}{$i18n.menu.profile}{$i18n.menu.group}{$i18n.menu.plugins}{$i18n.menu.forum}{$i18n.menu.help}
+
+ {* end of top menu for authorized user *} - - - - + {/if} + +
  - {if !$user->isClient()} - {$i18n.menu.time} + {* sub menu for authorized user *} +
+ + + {if $user->exists() && ($user->can('track_own_time') || $user->can('track_time'))} + + {if $user->isPluginEnabled('pu') && $user->isOptionEnabled('puncher_menu')} + + {/if} + {if $user->isPluginEnabled('wv') && $user->isOptionEnabled('week_menu')} + + {/if} + {/if} + {if $user->exists() && $user->isPluginEnabled('ex') && ($user->can('track_own_expenses') || $user->can('track_expenses'))} + {/if} - {if $user->isPluginEnabled('ex') && !$user->isClient()} - · {$i18n.menu.expenses} + {if $user->exists() && ($user->can('view_own_reports') || $user->can('view_reports') || $user->can('view_all_reports') || $user->can('view_client_reports'))} + {/if} - {if !$user->isClient()}· {/if}{$i18n.menu.reports} - {if ($user->canManageTeam() || $user->isClient()) && $user->isPluginEnabled('iv')} - · {$i18n.title.invoices} + {if $user->exists() && $user->isPluginEnabled('ts') && ($user->can('track_own_time') || $user->can('track_time'))} + {/if} - {if ($user->isPluginEnabled('ch') && !$user->isClient()) && ($smarty.const.MODE_PROJECTS == $user->tracking_mode - || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode || $user->isPluginEnabled('cl'))} - · {$i18n.menu.charts} + {if $user->exists() && $user->isPluginEnabled('iv') && ($user->can('manage_invoices') || $user->can('view_client_invoices'))} + {/if} - {if !$user->isClient() && ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - · {$i18n.menu.projects} + {if ($user->exists() && $user->isPluginEnabled('ch') && ($user->can('view_own_charts') || $user->can('view_charts') || $user->can('view_all_charts'))) && + (constant('MODE_PROJECTS') == $user->getTrackingMode() || constant('MODE_PROJECTS_AND_TASKS') == $user->getTrackingMode() || + $user->isPluginEnabled('cl'))} + {/if} - {if $user->canManageTeam() && ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - · {$i18n.menu.tasks} + {if ($user->can('view_own_projects') || $user->can('manage_projects')) && (constant('MODE_PROJECTS') == $user->getTrackingMode() || constant('MODE_PROJECTS_AND_TASKS') == $user->getTrackingMode())} + {/if} - {if !$user->isClient()} - · {$i18n.menu.users} + {if (constant('MODE_PROJECTS_AND_TASKS') == $user->getTrackingMode() && ($user->can('view_own_tasks') || $user->can('manage_tasks')))} + {/if} - {if $user->canManageTeam() && $user->isPluginEnabled('cl')} - · {$i18n.menu.clients} + {if $user->can('view_users') || $user->can('manage_users')} + {/if} - {if $user->isManager()} - · {$i18n.menu.export} + {if $user->isPluginEnabled('cl') && ($user->can('view_own_clients') || $user->can('manage_clients'))} + {/if} - - -
{$i18n.menu.time}{$i18n.menu.puncher}{$i18n.menu.week}{$i18n.menu.expenses}{$i18n.menu.reports}{$i18n.menu.timesheets}{$i18n.title.invoices}{$i18n.menu.charts}{$i18n.menu.projects}{$i18n.menu.tasks}{$i18n.menu.users}{$i18n.menu.clients}
- + {if $user->can('export_data')} +
{$i18n.menu.export}
+ + {* end of sub menu for authorized user *} {/if} {else} - - - - + + +
  - {$i18n.menu.login} · - {if isTrue($smarty.const.MULTITEAM_MODE) && $smarty.const.AUTH_MODULE == 'db'} - {$i18n.menu.create_team} · + {* top menu for non authorized user *} +
+ + + + {if isTrue('MULTIORG_MODE') && constant('AUTH_MODULE') == 'db'} + {/if} - {$i18n.menu.forum} · - {$i18n.menu.help} - - -
{$i18n.menu.login}{$i18n.menu.register}
+
{$i18n.menu.forum}{$i18n.menu.help}
+ {/if} -
+{* end of top menu for large screens *} - -{if $title} - - - -
{$title}{if $timestring}: {$timestring}{/if}
{$user->name|escape}{if $user->isAdmin()} {$i18n.label.role_admin}{elseif $user->isManager()} {$i18n.label.role_manager}{elseif $user->canManageTeam()} {$i18n.label.role_comanager}{/if}{if $user->behalf_id > 0} {$i18n.label.on_behalf} {$user->behalf_name|escape}{/if}{if $user->team}, {$user->team|escape}{/if}
+{* page title and user details *} +{if isset($title) && $title} +
{$title}{if isset($timestring) && $timestring}: {$timestring}{/if}
+ {if (isset($authenticated) && $authenticated)} +
{$user->getUserPartForHeader()}
{* No need to escape as it is done in the class. *} + {/if} {/if} - +{* end of page title and user details *} - +{* output errors *} {if $err->yes()} - - - - -
+
{foreach $err->getErrors() as $error} - {$error.message}
{* No need to escape as they are not coming from user and may contain a link. *} +{$error.message}
{* No need to escape as they are not coming from user and may contain a link. *} {/foreach} -
+ {/if} - +{* end of output errors *} - +{* output messages *} {if $msg->yes()} - - - - -
+
{foreach $msg->getErrors() as $message} - {$message.message}
{* No need to escape. *} +{$message.message}
{* No need to escape. *} {/foreach} -
+ {/if} - +{* end of output messages *} + +
diff --git a/WEB-INF/templates/import.tpl b/WEB-INF/templates/import.tpl index b85bc757a..5d7cd33ac 100644 --- a/WEB-INF/templates/import.tpl +++ b/WEB-INF/templates/import.tpl @@ -1,20 +1,15 @@ -{$forms.importForm.open} +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} - +
{$i18n.form.import.hint}
+{$forms.importForm.open} +
+ - + + +
-{if $user->isAdmin()} - - - - - - - - -
{$i18n.form.import.hint}
 
{$i18n.form.import.file}:{$forms.importForm.xmlfile.control}
{$forms.importForm.btn_submit.control}
-{/if} -
{$forms.importForm.xmlfile.control}
-{$forms.importForm.close} +
{$forms.importForm.btn_submit.control}
+{$forms.importForm.open} diff --git a/WEB-INF/templates/invoice_add.tpl b/WEB-INF/templates/invoice_add.tpl index e166b089f..78e4f5ba2 100644 --- a/WEB-INF/templates/invoice_add.tpl +++ b/WEB-INF/templates/invoice_add.tpl @@ -1,46 +1,50 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.invoiceForm.open} - +
+ + + + + + + - + + + + + + + + + + + + +
{$forms.invoiceForm.number.control}
- - - - - - - - - - - - - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - + + + + + + + + + + +{if isset($show_project) && $show_project} + + + + + + {/if} - - - - - - - - - - - - - - - - -
{$i18n.form.invoice.number} (*):{$forms.invoiceForm.number.control}
{$i18n.label.date} (*):{$forms.invoiceForm.date.control}
{$i18n.label.client} (*):{$forms.invoiceForm.client.control}
{$i18n.label.project}:{$forms.invoiceForm.project.control}
{$forms.invoiceForm.date.control}
{$forms.invoiceForm.client.control}
{$forms.invoiceForm.project.control}
{$i18n.label.start_date} (*):{$forms.invoiceForm.start.control}
{$i18n.label.end_date} (*):{$forms.invoiceForm.finish.control}
{$i18n.label.required_fields}
 
{$forms.invoiceForm.btn_submit.control}
-
{$forms.invoiceForm.start.control}
{$forms.invoiceForm.finish.control}
{$i18n.label.required_fields}
+
{$forms.invoiceForm.btn_submit.control}
{$forms.invoiceForm.close} + {$forms.invoiceDeleteForm.open} - +
+ - + + + + + + + +
{$i18n.form.invoice.invoice_to_delete}:
- - - - - - - - - - - - - - -
{$i18n.form.invoice.invoice_to_delete}:{$invoice_to_delete|escape}
{$i18n.form.invoice.invoice_entries}:{$forms.invoiceDeleteForm.delete_invoice_entries.control}
 
{$forms.invoiceDeleteForm.btn_delete.control}  {$forms.invoiceDeleteForm.btn_cancel.control}
+
{$i18n.form.invoice.invoice_to_delete}:{$invoice_to_delete|escape}
{$forms.invoiceDeleteForm.delete_invoice_entries.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it}
+
{$forms.invoiceDeleteForm.btn_delete.control} {$forms.invoiceDeleteForm.btn_cancel.control}
{$forms.invoiceDeleteForm.close} diff --git a/WEB-INF/templates/invoice_view.tpl b/WEB-INF/templates/invoice_view.tpl index 31ff8e76a..801245559 100644 --- a/WEB-INF/templates/invoice_view.tpl +++ b/WEB-INF/templates/invoice_view.tpl @@ -1,70 +1,104 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + - +
{$i18n.title.invoice} {$invoice_name|escape}
+
- + + - + +{if isset($client_address)} + + + + +{/if} +
- - - - - -
{$i18n.title.invoice} {$invoice_name|escape}
{$i18n.label.date}: {$invoice_date}
{$i18n.label.client}: {$client_name|escape}
{$i18n.label.client_address}: {$client_address|escape}
-
{$i18n.label.date}:{$invoice_date}
+ {$i18n.label.client}:{$client_name|escape}
{$i18n.label.client_address}:{$client_address|escape}
+
+ {if $invoice_items} - - - - - {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - +
{$i18n.label.date}{$i18n.form.invoice.person}{$i18n.label.project}
+ + + + {if $show_project} + {/if} - {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + {if $show_task} + {/if} - - - - + + + + {if $user->isPluginEnabled('ps')} + + {/if} + {foreach $invoice_items as $invoice_item} - - - - {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + + + + {if $show_project} + + {/if} + {if $show_task} + {/if} - {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + + + + {if $show_paid_column} + {/if} - - - - + {/foreach} - - {if $tax} - - - - - - - - - {/if} - - - - -
{$i18n.label.date}{$i18n.form.invoice.person}{$i18n.label.project}{$i18n.label.task}{$i18n.label.task}{$i18n.label.note}{$i18n.label.duration}{$i18n.label.cost}
{$i18n.label.note}{$i18n.label.duration}{$i18n.label.cost}{$i18n.label.paid}
{$invoice_item.date}{$invoice_item.user_name|escape}{$invoice_item.project_name|escape}
{$invoice_item.date}{$invoice_item.user_name|escape}{$invoice_item.project_name|escape}{$invoice_item.task_name|escape}{$invoice_item.task_name|escape}{$invoice_item.note|escape}{$invoice_item.duration}{$invoice_item.cost}{if $invoice_item.paid}{$i18n.label.yes}{else}{$i18n.label.no}{/if}{$invoice_item.note|escape}{$invoice_item.duration}{$invoice_item.cost}
 
{$i18n.label.subtotal}:{$subtotal|escape}
{$i18n.label.tax}:{$tax|escape}
{$i18n.label.total}:{$total|escape}
+ {if isset($tax)} +
{$i18n.label.subtotal}:{$subtotal|escape}
{$i18n.label.tax}:{$tax|escape}
{$i18n.label.total}:{$total|escape}
{/if} - + +{if isset($show_mark_paid)} +{$forms.invoiceForm.open} + + + + + - +
{$forms.invoiceForm.mark_paid_action_options.control} {$forms.invoiceForm.btn_mark_paid.control}

- -
+{$forms.invoiceForm.close} +{/if} +
+
+ +
+
diff --git a/WEB-INF/templates/invoices.tpl b/WEB-INF/templates/invoices.tpl index 4d6f50a90..003266cb0 100644 --- a/WEB-INF/templates/invoices.tpl +++ b/WEB-INF/templates/invoices.tpl @@ -1,40 +1,56 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + - +{if $show_sorting_options} +{$forms.invoicesForm.open} +
+ - + + + + + + + + + +
-{if $user->canManageTeam() || $user->isClient()} - - - - - - - {if !$user->isClient()} - - {/if} - - {foreach $invoices as $invoice} - - - - - - {if !$user->isClient()} - - {/if} - - {/foreach} -
{$i18n.label.invoice}{$i18n.label.client}{$i18n.label.date}{$i18n.label.view}{$i18n.label.delete}
{$invoice.name|escape}{$invoice.client_name|escape}{$invoice.date}{$i18n.label.view}{$i18n.label.delete}
+
{$forms.invoicesForm.sort_option_1.control} {$forms.invoicesForm.sort_order_1.control}
{$forms.invoicesForm.sort_option_2.control} {$forms.invoicesForm.sort_order_2.control}
+{$forms.invoicesForm.close} +{/if} + + + + + +{if $user->isPluginEnabled('ps')} + +{/if} +{if !$user->isClient()} + +{/if} + +{foreach $invoices as $invoice} + + + + + {if $user->isPluginEnabled('ps')} + + {/if} {if !$user->isClient()} -
{$i18n.label.invoice}{$i18n.label.client}{$i18n.label.date}{$i18n.label.paid}
{$invoice.name|escape}{$invoice.client|escape}{$invoice.date}{if $invoice.paid}{$i18n.label.yes}{else}{$i18n.label.no}{/if}
- -

+ {$i18n.label.delete} {/if} -{/if} - +{/foreach} + +{if !$user->isClient()} +
+{/if} diff --git a/WEB-INF/templates/locking.tpl b/WEB-INF/templates/locking.tpl index 177f59188..8ecf48cc9 100644 --- a/WEB-INF/templates/locking.tpl +++ b/WEB-INF/templates/locking.tpl @@ -1,20 +1,17 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.lockingForm.open} - +
+ - + +
- - - - - - - - - - - -
{$i18n.label.cron_schedule}:{$forms.lockingForm.lock_spec.control} {$i18n.label.what_is_it}
 
{$forms.lockingForm.btn_save.control}
+
{$forms.lockingForm.lock_spec.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it}
+
{$forms.lockingForm.btn_save.control}
{$forms.lockingForm.close} diff --git a/WEB-INF/templates/login.db.tpl b/WEB-INF/templates/login.db.tpl index 977d9b996..3d5aa9667 100644 --- a/WEB-INF/templates/login.db.tpl +++ b/WEB-INF/templates/login.db.tpl @@ -1,21 +1,23 @@ - +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +
+ - {$i18n.label.login}: - + + + + - {$i18n.label.password}: - + + - - + + - - - - +
{$forms.loginForm.login.control}{$forms.loginForm.login.control}
{$forms.loginForm.password.control}{$forms.loginForm.password.control}
 {$i18n.form.login.forgot_password}{$i18n.form.login.forgot_password}
 
{$forms.loginForm.btn_login.control}{$forms.loginForm.btn_login.control}
- \ No newline at end of file diff --git a/WEB-INF/templates/login.ldap.tpl b/WEB-INF/templates/login.ldap.tpl index bf285dc29..e60d954ee 100644 --- a/WEB-INF/templates/login.ldap.tpl +++ b/WEB-INF/templates/login.ldap.tpl @@ -1,28 +1,26 @@ - - {if $show_hint} +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{if $show_hint} +
{$i18n.label.ldap_hint}
+{/if} +
+ - + + + + - + + - {/if} - {$i18n.label.login}: - + + - {$i18n.label.password}: - - - - - - - - - - +
{$i18n.label.ldap_hint}{$forms.loginForm.login.control}@{$Auth_ldap_params.default_domain}
 {$forms.loginForm.password.control}
{$forms.loginForm.login.control} @{$Auth_ldap_params.default_domain}{$i18n.form.login.forgot_password}
{$forms.loginForm.password.control}
 
 
{$forms.loginForm.btn_login.control}{$forms.loginForm.btn_login.control}
- \ No newline at end of file diff --git a/WEB-INF/templates/login.tpl b/WEB-INF/templates/login.tpl index d7d9b93f4..e6a8a4ed3 100644 --- a/WEB-INF/templates/login.tpl +++ b/WEB-INF/templates/login.tpl @@ -1,21 +1,17 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + - - - - -
- {$forms.loginForm.open} - {include file="login.`$smarty.const.AUTH_MODULE`.tpl"} - {$forms.loginForm.close} -
+ +{$forms.loginForm.open} +{include file="login.`$smarty.const.AUTH_MODULE`.tpl"} +{$forms.loginForm.close} {if !empty($about_text)} -
{$about_text}
+
{$about_text}
{/if} diff --git a/WEB-INF/templates/mail.tpl b/WEB-INF/templates/mail.tpl index 4dcf34b79..3a3cf132f 100644 --- a/WEB-INF/templates/mail.tpl +++ b/WEB-INF/templates/mail.tpl @@ -1,43 +1,34 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.mailForm.open} - - - - +
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.form.mail.from} (*):{$sender}
{$i18n.form.mail.to} (*):{$forms.mailForm.receiver.control}
{$i18n.form.mail.cc}:{$forms.mailForm.cc.control}
{$i18n.form.mail.subject} (*):{$forms.mailForm.subject.control}
{$i18n.label.comment}:{$forms.mailForm.comment.control}
{$i18n.label.required_fields}
{$forms.mailForm.btn_send.control}
-
-
+ + + + + + + + + + + + + + + + + + + + + + + + + +
{$forms.mailForm.receiver.control}
{$forms.mailForm.cc.control}
{$forms.mailForm.subject.control}
{$forms.mailForm.comment.control}
{$i18n.label.required_fields}
+
{$forms.mailForm.btn_send.control}
{$forms.mailForm.close} diff --git a/WEB-INF/templates/mobile/client_add.tpl b/WEB-INF/templates/mobile/client_add.tpl deleted file mode 100644 index 8db93a418..000000000 --- a/WEB-INF/templates/mobile/client_add.tpl +++ /dev/null @@ -1,37 +0,0 @@ -{$forms.clientForm.open} - - - - -
- - - - - - - - - - - - - - - - - - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - - -{/if} - - - -
{$i18n.label.client_name} (*):{$forms.clientForm.name.control}
{$i18n.label.client_address}:{$forms.clientForm.address.control}
{$i18n.label.tax}, %:{$forms.clientForm.tax.control} (0{$user->decimal_mark}00)
{$i18n.label.required_fields}
 
{$i18n.label.projects}:{$forms.clientForm.projects.control}
 
{$forms.clientForm.btn_submit.control}
-
-{$forms.clientForm.close} diff --git a/WEB-INF/templates/mobile/client_delete.tpl b/WEB-INF/templates/mobile/client_delete.tpl deleted file mode 100644 index f40b94c12..000000000 --- a/WEB-INF/templates/mobile/client_delete.tpl +++ /dev/null @@ -1,25 +0,0 @@ -{$forms.clientDeleteForm.open} - - - - -
- - - - - - - - - - - - - - - - -
{$i18n.form.client.client_to_delete}:{$client_to_delete|escape}
{$i18n.form.client.client_entries}:{$forms.clientDeleteForm.delete_client_entries.control}
 
{$forms.clientDeleteForm.btn_delete.control}  {$forms.clientDeleteForm.btn_cancel.control}
-
-{$forms.clientDeleteForm.close} diff --git a/WEB-INF/templates/mobile/client_edit.tpl b/WEB-INF/templates/mobile/client_edit.tpl deleted file mode 100644 index d01731f0a..000000000 --- a/WEB-INF/templates/mobile/client_edit.tpl +++ /dev/null @@ -1,41 +0,0 @@ -{$forms.clientForm.open} - - - - -
- - - - - - - - - - - - - - - - - - - - - - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - -{/if} - - - - -
{$i18n.label.client_name} (*):{$forms.clientForm.name.control}
{$i18n.label.client_address}:{$forms.clientForm.address.control}
{$i18n.label.tax}, %:{$forms.clientForm.tax.control} (0{$user->decimal_mark}00)
{$i18n.label.status}:{$forms.clientForm.status.control}
{$i18n.label.required_fields}
 
{$i18n.label.projects}:{$forms.clientForm.projects.control}
 
{$forms.clientForm.btn_save.control} {$forms.clientForm.btn_copy.control} {$forms.clientForm.btn_delete.control}
-
-{$forms.clientForm.close} diff --git a/WEB-INF/templates/mobile/clients.tpl b/WEB-INF/templates/mobile/clients.tpl deleted file mode 100644 index 00a19eee7..000000000 --- a/WEB-INF/templates/mobile/clients.tpl +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - -
-{if ($user->canManageTeam())} - - {if $inactive_clients} - - {/if} - - - - - {foreach $active_clients as $client} - - - - - {/foreach} -
{$i18n.form.clients.active_clients}
{$i18n.label.person_name}{$i18n.label.address}
{$client.name|escape}{$client.address|escape}
- - - -

- - {if $inactive_clients} - - - - - - - {foreach $inactive_clients as $client} - - - - - {/foreach} -
{$i18n.form.clients.inactive_clients}
{$i18n.label.person_name}{$i18n.label.address}
{$client.name|escape}{$client.address|escape}
- - - -

- {/if} -{/if} -
diff --git a/WEB-INF/templates/mobile/expense_delete.tpl b/WEB-INF/templates/mobile/expense_delete.tpl deleted file mode 100644 index a1c0fd783..000000000 --- a/WEB-INF/templates/mobile/expense_delete.tpl +++ /dev/null @@ -1,39 +0,0 @@ -{$forms.expenseItemForm.open} - - - - -
- - -{if $user->isPluginEnabled('cl')} - -{/if} - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - -{/if} - - - - -{if $user->isPluginEnabled('cl')} - -{/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - -{/if} - - - -
{$i18n.label.client}{$i18n.label.project}{$i18n.label.item}{$i18n.label.cost}
{$expense_item.client_name|escape}{$expense_item.project_name|escape}{$expense_item.name|escape}{$expense_item.cost}
- - - - - - - -
 
{$forms.expenseItemForm.delete_button.control}  {$forms.expenseItemForm.cancel_button.control}
-
-{$forms.expenseItemForm.close} diff --git a/WEB-INF/templates/mobile/expense_edit.tpl b/WEB-INF/templates/mobile/expense_edit.tpl deleted file mode 100644 index a9c095807..000000000 --- a/WEB-INF/templates/mobile/expense_edit.tpl +++ /dev/null @@ -1,117 +0,0 @@ - - -{$forms.expenseItemForm.open} - - - - -
- - - - -
- -{if $user->isPluginEnabled('cl')} - - - - -{/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - -{/if} - - - - - - - - - - - - - - - - - - - -
{$i18n.label.client} {if $user->isPluginEnabled('cm')}(*){/if}:{$forms.expenseItemForm.client.control}
{$i18n.label.project} (*):{$forms.expenseItemForm.project.control}
{$i18n.label.item}:{$forms.expenseItemForm.item_name.control}
{$i18n.label.cost}:{$forms.expenseItemForm.cost.control} {$user->currency|escape}
{$i18n.label.date}:{$forms.expenseItemForm.date.control}
 
{$forms.expenseItemForm.btn_save.control} {$forms.expenseItemForm.btn_copy.control} {$forms.expenseItemForm.btn_delete.control}
-
-
-{$forms.expenseItemForm.close} diff --git a/WEB-INF/templates/mobile/expenses.tpl b/WEB-INF/templates/mobile/expenses.tpl deleted file mode 100644 index 52de0a918..000000000 --- a/WEB-INF/templates/mobile/expenses.tpl +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - -
<<{$timestring}>>
- -{$forms.expensesForm.open} - - - - - -
- -{if $on_behalf_control} - - - - -{/if} -{if $user->isPluginEnabled('cl')} - - - - -{/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - -{/if} - - - - - - - - -
{$i18n.label.user}:{$forms.expensesForm.onBehalfUser.control}
{$i18n.label.client}{if $user->isPluginEnabled('cm')} (*){/if}:{$forms.expensesForm.client.control}
{$i18n.label.project} (*):{$forms.expensesForm.project.control}
{$i18n.label.item} (*):{$forms.expensesForm.item_name.control}
{$i18n.label.cost} (*):{$forms.expensesForm.cost.control} {$user->currency|escape}
-
- - - - - -
{$forms.expensesForm.btn_submit.control}
- - - - - -
-{if $expense_items} - - - {if $user->isPluginEnabled('cl')} - - {/if} - {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - {/if} - - - - {foreach $expense_items as $item} - - {if $user->isPluginEnabled('cl')} - - {/if} - {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - {/if} - - - - {/foreach} -
{$i18n.label.client}{$i18n.label.project}{$i18n.label.item}{$i18n.label.cost}
{$item.client|escape}{$item.project|escape}{if $item.invoice_id} {$item.item|escape} {else}{$item.item|escape}{/if}{$item.cost}
- - - - -
{$i18n.label.day_total}: {$user->currency|escape} {$day_total}
-{/if} -
-{$forms.expensesForm.close} diff --git a/WEB-INF/templates/mobile/header.tpl b/WEB-INF/templates/mobile/header.tpl deleted file mode 100644 index 87d2c4012..000000000 --- a/WEB-INF/templates/mobile/header.tpl +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - -{if $i18n.language.rtl} - -{/if} - Time Tracker{if $title} - {$title}{/if} - - - - - - - -{assign var="tab_width" value="300"} - - - -
- - - - -{if $user->custom_logo} - - -
-{else} - -{/if} - - - - -
- - - -{if $user->custom_logo} - -{else} - -{/if} - -
Time TrackerAnuko Time Tracker
-
-
- - -{if $authenticated} - {if $user->isAdmin()} - - - - - - -
  - {$i18n.menu.teams} · - {$i18n.menu.options} -
- - {else} - - - - - - -
  - {if !$user->isClient()} - {$i18n.menu.time} - {/if} - {if $user->isPluginEnabled('ex') && !$user->isClient()} - · {$i18n.menu.expenses} - {/if} - {if !$user->isClient() && ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - · {$i18n.menu.projects} - {/if} - {if $user->canManageTeam() && ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - · {$i18n.menu.tasks} - {/if} - {if !$user->isClient()} - · {$i18n.menu.users} - {/if} - {if $user->canManageTeam() && $user->isPluginEnabled('cl')} - · {$i18n.menu.clients} - {/if} -
- - {/if} -{/if} - - -{if $err->yes()} - - - - -
- {foreach $err->getErrors() as $error} - {$error.message}
{* No need to escape as they are not coming from user and may contain a link. *} - {/foreach} -
-{/if} - - - -{if $msg->yes()} - - - - -
- {foreach $msg->getErrors() as $message} - {$message.message}
{* No need to escape. *} - {/foreach} -
-{/if} - diff --git a/WEB-INF/templates/mobile/index.tpl b/WEB-INF/templates/mobile/index.tpl deleted file mode 100644 index babdac50e..000000000 --- a/WEB-INF/templates/mobile/index.tpl +++ /dev/null @@ -1,3 +0,0 @@ -{include file="mobile/header.tpl"} - -{if $content_page_name}{include file="$content_page_name"}{/if} diff --git a/WEB-INF/templates/mobile/login.db.tpl b/WEB-INF/templates/mobile/login.db.tpl deleted file mode 100644 index 2968b1dab..000000000 --- a/WEB-INF/templates/mobile/login.db.tpl +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - -
{$i18n.label.login}:
{$forms.loginForm.login.control}
{$i18n.label.password}:
{$forms.loginForm.password.control}
{$forms.loginForm.btn_login.control}
diff --git a/WEB-INF/templates/mobile/login.ldap.tpl b/WEB-INF/templates/mobile/login.ldap.tpl deleted file mode 100644 index 2968b1dab..000000000 --- a/WEB-INF/templates/mobile/login.ldap.tpl +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - -
{$i18n.label.login}:
{$forms.loginForm.login.control}
{$i18n.label.password}:
{$forms.loginForm.password.control}
{$forms.loginForm.btn_login.control}
diff --git a/WEB-INF/templates/mobile/login.tpl b/WEB-INF/templates/mobile/login.tpl deleted file mode 100644 index 1a2e888e4..000000000 --- a/WEB-INF/templates/mobile/login.tpl +++ /dev/null @@ -1,17 +0,0 @@ - - - - - -
- {$forms.loginForm.open} - {include file="mobile/login.`$smarty.const.AUTH_MODULE`.tpl"} - {$forms.loginForm.close} -
diff --git a/WEB-INF/templates/mobile/project_add.tpl b/WEB-INF/templates/mobile/project_add.tpl deleted file mode 100644 index 3aad22c20..000000000 --- a/WEB-INF/templates/mobile/project_add.tpl +++ /dev/null @@ -1,42 +0,0 @@ -{$forms.projectForm.open} - - - - -
- - - - - - - - - - - - - - - -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - - -{/if} - - - - - - - - - - - -
{$i18n.label.thing_name} (*):{$forms.projectForm.project_name.control}
{$i18n.label.description}:{$forms.projectForm.description.control}
 
{$i18n.label.users}:{$forms.projectForm.users.control}
 
{$i18n.label.tasks}:{$forms.projectForm.tasks.control}
{$i18n.label.required_fields}
 
{$forms.projectForm.btn_add.control}
-
-{$forms.projectForm.close} diff --git a/WEB-INF/templates/mobile/project_delete.tpl b/WEB-INF/templates/mobile/project_delete.tpl deleted file mode 100644 index 98d3e836d..000000000 --- a/WEB-INF/templates/mobile/project_delete.tpl +++ /dev/null @@ -1,18 +0,0 @@ -{$forms.projectDeleteForm.open} - - - - -
- - - - - - - - - -
{$project_to_delete|escape}
 
{$forms.projectDeleteForm.btn_delete.control}  {$forms.projectDeleteForm.btn_cancel.control}
-
-{$forms.projectDeleteForm.close} diff --git a/WEB-INF/templates/mobile/project_edit.tpl b/WEB-INF/templates/mobile/project_edit.tpl deleted file mode 100644 index 4a94bfeb9..000000000 --- a/WEB-INF/templates/mobile/project_edit.tpl +++ /dev/null @@ -1,45 +0,0 @@ -{$forms.projectForm.open} - - - - -
- - - - - - - - - - - - - - - - - - -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - - -{/if} - - - - - - - - - - - -
{$i18n.label.thing_name} (*):{$forms.projectForm.project_name.control}
{$i18n.label.description}:{$forms.projectForm.description.control}
{$i18n.label.status}:{$forms.projectForm.status.control}
 
{$i18n.label.users}:{$forms.projectForm.users.control}
 
{$i18n.label.tasks}:{$forms.projectForm.tasks.control}
{$i18n.label.required_fields}
 
{$forms.projectForm.btn_save.control} {$forms.projectForm.btn_copy.control} {$forms.projectForm.btn_delete.control}
-
-{$forms.projectForm.close} diff --git a/WEB-INF/templates/mobile/projects.tpl b/WEB-INF/templates/mobile/projects.tpl deleted file mode 100644 index d40e792a8..000000000 --- a/WEB-INF/templates/mobile/projects.tpl +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - -
-{if $user->canManageTeam()} - - {if $inactive_projects} - - {/if} - - - - - {if $active_projects} - {foreach $active_projects as $project} - - - - - {/foreach} - {/if} -
{$i18n.form.projects.active_projects}
{$i18n.label.thing_name}{$i18n.label.description}
{$project.name|escape}{$project.description|escape}
- - - - - -

-
-
- - {if $inactive_projects} - - - - - - - {foreach $inactive_projects as $project} - - - - - {/foreach} -
{$i18n.form.projects.inactive_projects}
{$i18n.label.thing_name}{$i18n.label.description}
{$project.name|escape}{$project.description|escape}
- - - - - -

-
-
- {/if} -{else} - - - - - - {if $active_projects} - {foreach $active_projects as $project} - - - - - {/foreach} - {/if} -
{$i18n.label.thing_name}{$i18n.label.description}
{$project.name|escape}{$project.description|escape}
-{/if} -
diff --git a/WEB-INF/templates/mobile/task_add.tpl b/WEB-INF/templates/mobile/task_add.tpl deleted file mode 100644 index 68e415e1c..000000000 --- a/WEB-INF/templates/mobile/task_add.tpl +++ /dev/null @@ -1,33 +0,0 @@ -{$forms.taskForm.open} - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.thing_name} (*):{$forms.taskForm.name.control}
{$i18n.label.description}:{$forms.taskForm.description.control}
{$i18n.label.projects}:{$forms.taskForm.projects.control}
{$i18n.label.required_fields}
 
{$forms.taskForm.btn_submit.control}
-
-{$forms.taskForm.close} diff --git a/WEB-INF/templates/mobile/task_delete.tpl b/WEB-INF/templates/mobile/task_delete.tpl deleted file mode 100644 index 9f16d7c3a..000000000 --- a/WEB-INF/templates/mobile/task_delete.tpl +++ /dev/null @@ -1,20 +0,0 @@ -{$forms.taskDeleteForm.open} - - - - -
- - - - - - - - - - - -
{$task_to_delete|escape}
 
{$forms.taskDeleteForm.btn_delete.control}  {$forms.taskDeleteForm.btn_cancel.control}
-
-{$forms.taskDeleteForm.close} diff --git a/WEB-INF/templates/mobile/task_edit.tpl b/WEB-INF/templates/mobile/task_edit.tpl deleted file mode 100644 index f6672c739..000000000 --- a/WEB-INF/templates/mobile/task_edit.tpl +++ /dev/null @@ -1,37 +0,0 @@ -{$forms.taskForm.open} - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.thing_name} (*):{$forms.taskForm.name.control}
{$i18n.label.description}:{$forms.taskForm.description.control}
{$i18n.label.status}:{$forms.taskForm.status.control}
{$i18n.label.projects}:{$forms.taskForm.projects.control}
{$i18n.label.required_fields}
 
{$forms.taskForm.btn_save.control} {$forms.taskForm.btn_copy.control} {$forms.taskForm.btn_delete.control}
-
-{$forms.taskForm.close} diff --git a/WEB-INF/templates/mobile/tasks.tpl b/WEB-INF/templates/mobile/tasks.tpl deleted file mode 100644 index 8ed15fb8a..000000000 --- a/WEB-INF/templates/mobile/tasks.tpl +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - -
-{if $user->canManageTeam()} - - {if $inactive_tasks} - - {/if} - - - - - {if $active_tasks} - {foreach $active_tasks as $task} - - - - - {/foreach} - {/if} -
{$i18n.form.tasks.active_tasks}
{$i18n.label.thing_name}{$i18n.label.description}
{$task.name|escape}{$task.description|escape}
- - - - - -

-
-
- - {if $inactive_tasks} - - - - - - - {foreach $inactive_tasks as $task} - - - - - {/foreach} -
{$i18n.form.tasks.inactive_tasks}
{$i18n.label.thing_name}{$i18n.label.description}
{$task.name|escape}{$task.description|escape}
- - - - - -

-
-
- {/if} -{else} - - - - - - {if $active_tasks} - {foreach $active_tasks as $task} - - - - - {/foreach} - {/if} -
{$i18n.label.thing_name}{$i18n.label.description}
{$task.name|escape}{$task.description|escape}
- {/if} -
diff --git a/WEB-INF/templates/mobile/time.tpl b/WEB-INF/templates/mobile/time.tpl deleted file mode 100644 index ce96dde4d..000000000 --- a/WEB-INF/templates/mobile/time.tpl +++ /dev/null @@ -1,94 +0,0 @@ -{include file="time_script.tpl"} - - - - - - - - - -
<<{$timestring}>>
- - - - - -
- {if $time_records} - - {foreach $time_records as $record} - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - -{/if} - - - - {/foreach} -
{$record.project|escape}{if ($record.duration == '0:00' && $record.start <> '')}{/if}{$record.duration}{if ($record.duration == '0:00' && $record.start <> '')}{/if}{if $record.invoice_id} {else}{$i18n.label.edit}{/if}
- - - - - -
{$i18n.label.day_total}:{$day_total}
- {/if} -
- -{$forms.timeRecordForm.open} - - - - -
- - - - - - - -
- -{if $user->isPluginEnabled('cl')} - - -{/if} -{if $user->isPluginEnabled('iv')} - -{/if} -{if ($custom_fields && $custom_fields->fields[0])} - - -{/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - -{/if} -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - -{/if} -{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - - - - -{/if} -{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - -{/if} - - - -
{$i18n.label.client}:
{$forms.timeRecordForm.client.control}
{$custom_fields->fields[0]['label']|escape}:
{$forms.timeRecordForm.cf_1.control}
{$i18n.label.project}:
{$forms.timeRecordForm.project.control}
{$i18n.label.task}:
{$forms.timeRecordForm.task.control}
{$i18n.label.start}:
{$forms.timeRecordForm.start.control} 
{$i18n.label.finish}:
{$forms.timeRecordForm.finish.control} 
{$i18n.label.duration}:
{$forms.timeRecordForm.duration.control}
{$i18n.label.note}:
{$forms.timeRecordForm.note.control}
-
{$forms.timeRecordForm.btn_submit.control}
-
-{$forms.timeRecordForm.close} diff --git a/WEB-INF/templates/mobile/time_delete.tpl b/WEB-INF/templates/mobile/time_delete.tpl deleted file mode 100644 index 0428e13b1..000000000 --- a/WEB-INF/templates/mobile/time_delete.tpl +++ /dev/null @@ -1,32 +0,0 @@ -{$forms.timeRecordForm.open} - - - - -
- - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - -{/if} - - - - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - -{/if} - - - -
{$i18n.label.project}{$i18n.label.duration}{$i18n.label.note}
{$time_rec.project_name|escape}{if ($time_rec.duration == '0:00' && $time_rec.start <> '')}{$i18n.form.time.uncompleted}{else}{$time_rec.duration}{/if}{if $time_rec.comment}{$time_rec.comment|escape}{else} {/if}
- - - - - - - -
 
{$forms.timeRecordForm.delete_button.control}  {$forms.timeRecordForm.cancel_button.control}
-
-{$forms.timeRecordForm.close} diff --git a/WEB-INF/templates/mobile/time_edit.tpl b/WEB-INF/templates/mobile/time_edit.tpl deleted file mode 100644 index 8c6a14142..000000000 --- a/WEB-INF/templates/mobile/time_edit.tpl +++ /dev/null @@ -1,52 +0,0 @@ -{include file="time_script.tpl"} - -{$forms.timeRecordForm.open} - - - - -
- - - - -
- -{if $user->isPluginEnabled('cl')} - - -{/if} -{if $user->isPluginEnabled('iv')} - -{/if} -{if ($custom_fields && $custom_fields->fields[0])} - - -{/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - -{/if} -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - -{/if} -{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - - - -{/if} -{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - -{/if} - - - - - -
{$i18n.label.client}:
{$forms.timeRecordForm.client.control}
{$custom_fields->fields[0]['label']|escape}:
{$forms.timeRecordForm.cf_1.control}
{$i18n.label.project}:
{$forms.timeRecordForm.project.control}
{$i18n.label.task}:
{$forms.timeRecordForm.task.control}
{$i18n.label.start}:
{$forms.timeRecordForm.start.control} 
{$i18n.label.finish}:
{$forms.timeRecordForm.finish.control} 
{$i18n.label.duration}:
{$forms.timeRecordForm.duration.control}
{$i18n.label.date}:
{$forms.timeRecordForm.date.control}
{$i18n.label.note}:
{$forms.timeRecordForm.note.control}
{$forms.timeRecordForm.btn_save.control} {$forms.timeRecordForm.btn_delete.control}
-
-
-{$forms.timeRecordForm.close} diff --git a/WEB-INF/templates/mobile/timer.tpl b/WEB-INF/templates/mobile/timer.tpl deleted file mode 100644 index 631f4977d..000000000 --- a/WEB-INF/templates/mobile/timer.tpl +++ /dev/null @@ -1,115 +0,0 @@ -{include file="time_script.tpl"} - -

00:00 - - - -{if $uncompleted} - -{/if} - -{$forms.timeRecordForm.open} - - - - -
- - - - - - - -
- -{if $user->isPluginEnabled('cl')} - - -{/if} -{if $user->isPluginEnabled('iv')} - -{/if} -{if ($custom_fields && $custom_fields->fields[0])} - - -{/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - -{/if} -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - -{/if} -
{$i18n.label.client}:
{$forms.timeRecordForm.client.control}
{$custom_fields->fields[0]['label']|escape}:
{$forms.timeRecordForm.cf_1.control}
{$i18n.label.project}:
{$forms.timeRecordForm.project.control}
{$i18n.label.task}:
{$forms.timeRecordForm.task.control}
-
{$forms.timeRecordForm.btn_start.control} {$forms.timeRecordForm.btn_stop.control}
-
-{$forms.timeRecordForm.close} - - - - - -
- {if $time_records} - - - - - - - - - -
{$i18n.label.day_total}:{$day_total}
{$i18n.label.week_total}:{$week_total}
- {/if} -
diff --git a/WEB-INF/templates/mobile/user_add.tpl b/WEB-INF/templates/mobile/user_add.tpl deleted file mode 100644 index 297140444..000000000 --- a/WEB-INF/templates/mobile/user_add.tpl +++ /dev/null @@ -1,83 +0,0 @@ - - -{$forms.userForm.open} - -
- - - - - - - - -{if !$auth_external} - - - - - - - - -{/if} - - - - -{if $user->isManager()} - - - - -{/if} - - - - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - - - - -{/if} - - - -
{$i18n.label.person_name} (*):{$forms.userForm.name.control}
{$i18n.label.login} (*):{$forms.userForm.login.control}
{$i18n.label.password} (*):{$forms.userForm.pas1.control}
{$i18n.label.confirm_password} (*):{$forms.userForm.pas2.control}
{$i18n.label.email}:{$forms.userForm.email.control}
{$i18n.form.users.role}:{$forms.userForm.role.control} {$forms.userForm.client.control}
{$i18n.form.users.default_rate} (0{$user->decimal_mark}00):{$forms.userForm.rate.control}
{$i18n.label.projects}:{$forms.userForm.projects.control}
{$i18n.label.required_fields}
{$forms.userForm.btn_submit.control}
-

-{$forms.userForm.close} diff --git a/WEB-INF/templates/mobile/user_delete.tpl b/WEB-INF/templates/mobile/user_delete.tpl deleted file mode 100644 index e85d97f1f..000000000 --- a/WEB-INF/templates/mobile/user_delete.tpl +++ /dev/null @@ -1,20 +0,0 @@ -{$forms.userDeleteForm.open} - - - - -
- - - - - - - - - - - -
{$user_to_delete|escape}
 
{$forms.userDeleteForm.btn_delete.control}  {$forms.userDeleteForm.btn_cancel.control}
-
-{$forms.userDeleteForm.close} diff --git a/WEB-INF/templates/mobile/user_edit.tpl b/WEB-INF/templates/mobile/user_edit.tpl deleted file mode 100644 index cbfded788..000000000 --- a/WEB-INF/templates/mobile/user_edit.tpl +++ /dev/null @@ -1,112 +0,0 @@ - - -{$forms.userForm.open} - -
- - - - - - - - -{if !$auth_external} - - - - - - - - -{/if} - - - - -{if $user->isManager() && ($user->id != $user_id)} - - - - -{/if} -{* Prohibit deactivating team manager. Deactivating others is ok. *} -{if $user->canManageTeam() && !($user->isManager() && $user->id == $user_id)} - - - - -{/if} - - - - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - -{/if} - - - - - - -
{$i18n.label.person_name} (*):{$forms.userForm.name.control}
{$i18n.label.login} (*):{$forms.userForm.login.control}
{$i18n.label.password} (*):{$forms.userForm.pas1.control}
{$i18n.label.confirm_password} (*):{$forms.userForm.pas2.control}
{$i18n.label.email}:{$forms.userForm.email.control}
{$i18n.form.users.role}:{$forms.userForm.role.control} {$forms.userForm.client.control}
{$i18n.label.status}:{$forms.userForm.status.control}
{$i18n.form.users.default_rate} (0{$user->decimal_mark}00):{$forms.userForm.rate.control}
{$i18n.label.projects}:{$forms.userForm.projects.control}
{$i18n.label.required_fields}
{$forms.userForm.btn_submit.control} {$forms.userForm.btn_delete.control}
- -{$forms.userForm.close} diff --git a/WEB-INF/templates/mobile/users.tpl b/WEB-INF/templates/mobile/users.tpl deleted file mode 100644 index 6ff3269a9..000000000 --- a/WEB-INF/templates/mobile/users.tpl +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - -
-{if $user->canManageTeam()} - - {if $inactive_users} - - {/if} - - - - - - {if $active_users} - {foreach $active_users as $u} - - - - {if $smarty.const.ROLE_MANAGER == $u.role} - - {elseif $smarty.const.ROLE_COMANAGER == $u.role} - - {elseif $smarty.const.ROLE_CLIENT == $u.role} - - {elseif $smarty.const.ROLE_USER == $u.role} - - {/if} - - {/foreach} - {/if} -
{$i18n.form.users.active_users}
{$i18n.label.person_name}{$i18n.label.login}{$i18n.form.users.role}
- {if $smarty.const.UNCOMPLETED_INDICATORS == $user->uncompleted_indicators} - - {/if} - {if $user->isManager()} - {$u.name|escape} - {else} - {if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)} - {$u.name|escape} - {else} - {$u.name|escape} - {/if} - {/if} - {$u.login|escape}{$i18n.form.users.manager}{$i18n.form.users.comanager}{$i18n.label.client}{$i18n.label.user}
- - - - - -

-
-
- - {if $inactive_users} - - - - - - - - - {foreach $inactive_users as $u} - - - - {if $smarty.const.ROLE_MANAGER == $u.role} - - {elseif $smarty.const.ROLE_COMANAGER == $u.role} - - {elseif $smarty.const.ROLE_CLIENT == $u.role} - - {elseif $smarty.const.ROLE_USER == $u.role} - - {/if} - {if $user->isManager()} - - - {else} - - - {/if} - - {/foreach} - -
{$i18n.form.users.inactive_users}
{$i18n.label.person_name}{$i18n.label.login}{$i18n.form.users.role}{$i18n.label.edit}
- {if $user->isManager()} - {$u.name|escape} - {else} - {if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}{$u.name|escape}{/if} - {/if} - {$u.login|escape}{$i18n.form.users.manager}{$i18n.form.users.comanager}{$i18n.label.client}{$i18n.label.user}{$i18n.label.edit}{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}{$i18n.label.edit}{/if}
- - - - - -
-
-
- {/if} -{else} - - - - - - - {foreach $active_users as $u} - - - - {if $smarty.const.ROLE_MANAGER == $u.role} - - {elseif $smarty.const.ROLE_COMANAGER == $u.role} - - {elseif $smarty.const.ROLE_CLIENT == $u.role} - - {elseif $smarty.const.ROLE_USER == $u.role} - - {/if} - - {/foreach} -
{$i18n.label.person_name}{$i18n.label.login}{$i18n.form.users.role}
{$u.name|escape}{$u.login|escape}{$i18n.form.users.manager}{$i18n.form.users.comanager}{$i18n.label.client}{$i18n.label.user}
-{/if} -
diff --git a/WEB-INF/templates/notification_add.tpl b/WEB-INF/templates/notification_add.tpl index ff1d3e726..b7e609126 100644 --- a/WEB-INF/templates/notification_add.tpl +++ b/WEB-INF/templates/notification_add.tpl @@ -1,30 +1,59 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + + {$forms.notificationForm.open} - +
+ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.fav_report} (*):{$forms.notificationForm.fav_report.control}
{$i18n.label.cron_schedule} (*):{$forms.notificationForm.cron_spec.control} {$i18n.label.what_is_it}
{$i18n.label.email} (*):{$forms.notificationForm.email.control}
{$i18n.label.required_fields}
 
{$forms.notificationForm.btn_add.control}
+
{$forms.notificationForm.fav_report.control}
{$forms.notificationForm.cron_spec.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.notificationForm.email.control}
{$forms.notificationForm.cc.control}
{$forms.notificationForm.subject.control}
{$forms.notificationForm.comment.control}
{$forms.notificationForm.report_condition.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it}
{$i18n.label.required_fields}
+
{$forms.notificationForm.btn_add.control}
{$forms.notificationForm.close} diff --git a/WEB-INF/templates/notification_delete.tpl b/WEB-INF/templates/notification_delete.tpl index fac015c3a..bd413463d 100644 --- a/WEB-INF/templates/notification_delete.tpl +++ b/WEB-INF/templates/notification_delete.tpl @@ -1,18 +1,7 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.notificationDeleteForm.open} - - - - -
- - - - - - - - - -
{$notification_to_delete|escape}
 
{$forms.notificationDeleteForm.btn_delete.control}  {$forms.notificationDeleteForm.btn_cancel.control}
-
+
{$notification_to_delete|escape}
+
{$forms.notificationDeleteForm.btn_delete.control} {$forms.notificationDeleteForm.btn_cancel.control}
{$forms.notificationDeleteForm.close} diff --git a/WEB-INF/templates/notification_edit.tpl b/WEB-INF/templates/notification_edit.tpl index ad3a7db06..1a0967cc6 100644 --- a/WEB-INF/templates/notification_edit.tpl +++ b/WEB-INF/templates/notification_edit.tpl @@ -1,30 +1,59 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + + {$forms.notificationForm.open} - +
+ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.fav_report} (*):{$forms.notificationForm.fav_report.control}
{$i18n.label.cron_schedule} (*):{$forms.notificationForm.cron_spec.control} {$i18n.label.what_is_it}
{$i18n.label.email} (*):{$forms.notificationForm.email.control}
{$i18n.label.required_fields}
 
{$forms.notificationForm.btn_submit.control}
+
{$forms.notificationForm.fav_report.control}
{$forms.notificationForm.cron_spec.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.notificationForm.email.control}
{$forms.notificationForm.cc.control}
{$forms.notificationForm.subject.control}
{$forms.notificationForm.comment.control}
{$forms.notificationForm.report_condition.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it}
{$i18n.label.required_fields}
+
{$forms.notificationForm.btn_submit.control}
{$forms.notificationForm.close} diff --git a/WEB-INF/templates/notifications.tpl b/WEB-INF/templates/notifications.tpl index 7d51f52ad..a1be398ea 100644 --- a/WEB-INF/templates/notifications.tpl +++ b/WEB-INF/templates/notifications.tpl @@ -1,34 +1,28 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.notificationsForm.open} - +
- + + + + + + +{if $notifications} + {foreach $notifications as $notification} + + + + + + + + + {/foreach} +{/if}
-{if $user->canManageTeam()} - - - - - - - - - {if $notifications} - {foreach $notifications as $notification} - - - - - - - - {/foreach} - {/if} -
{$i18n.label.thing_name}{$i18n.label.cron_schedule}{$i18n.label.email}{$i18n.label.edit}{$i18n.label.delete}
{$notification['name']|escape}{$notification['cron_spec']|escape}{$notification['email']|escape}{$i18n.label.edit}{$i18n.label.delete}
- - - -

{$forms.notificationsForm.btn_add.control}
-{/if} -
{$i18n.label.thing_name}{$i18n.label.schedule}{$i18n.label.email}{$i18n.label.condition}
{$notification['name']|escape}{$notification['cron_spec']|escape}{$notification['email']|escape}{$notification['report_condition']|escape}{$i18n.label.edit}{$i18n.label.delete}
+
{$forms.notificationsForm.btn_add.control}
{$forms.notificationsForm.close} diff --git a/WEB-INF/templates/password_change.tpl b/WEB-INF/templates/password_change.tpl index 797f4a358..bc2517a25 100644 --- a/WEB-INF/templates/password_change.tpl +++ b/WEB-INF/templates/password_change.tpl @@ -1,41 +1,25 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.newPasswordForm.open} - +
{$i18n.form.change_password.tip}
+
+
+ - + + + + + + + + + + + + +
-{if $result_message} - - -
{$result_message}
-{else} - - - - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.form.change_password.tip}
 
{$i18n.label.password} (*):{$forms.newPasswordForm.password1.control}
{$i18n.label.confirm_password} (*):{$forms.newPasswordForm.password2.control}
 
{$i18n.label.required_fields}
 {$forms.newPasswordForm.btn_save.control}
-{/if} -
{$forms.newPasswordForm.password1.control}
{$forms.newPasswordForm.password2.control}
{$i18n.label.required_fields}
{$forms.newPasswordForm.btn_save.control}
{$forms.newPasswordForm.close} diff --git a/WEB-INF/templates/password_reset.tpl b/WEB-INF/templates/password_reset.tpl index 93c5d49db..72664dbbc 100644 --- a/WEB-INF/templates/password_reset.tpl +++ b/WEB-INF/templates/password_reset.tpl @@ -1,27 +1,16 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.resetPasswordForm.open} - +
+ - + + + + + +
-{if $result_message} - - -
{$result_message}
-{else} - - - - - - - - - - - - -
{$i18n.label.login}:{$forms.resetPasswordForm.login.control}
 
 {$forms.resetPasswordForm.btn_submit.control}
-{/if} -
{$forms.resetPasswordForm.login.control}
{$forms.resetPasswordForm.btn_submit.control}
{$forms.resetPasswordForm.close} diff --git a/WEB-INF/templates/plugins.tpl b/WEB-INF/templates/plugins.tpl new file mode 100644 index 000000000..e4402f62d --- /dev/null +++ b/WEB-INF/templates/plugins.tpl @@ -0,0 +1,215 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + + + +{$forms.pluginsForm.open} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{$forms.pluginsForm.charts.control}
{$forms.pluginsForm.puncher.control} {$i18n.label.what_is_it}{if $user->isPluginEnabled('pu')}{$i18n.label.configure}{/if}
{$forms.pluginsForm.clients.control} {$forms.pluginsForm.client_required.control}
{$forms.pluginsForm.invoices.control}
{$forms.pluginsForm.paid_status.control}
{$forms.pluginsForm.custom_fields.control} {if $user->isPluginEnabled('cf')}{$i18n.label.configure}{/if}
{$forms.pluginsForm.expenses.control} {$forms.pluginsForm.tax_expenses.control} {if $user->isPluginEnabled('ex')}{$i18n.label.configure}{/if}
{$forms.pluginsForm.notifications.control} {$i18n.label.what_is_it}{if $user_exists && $user->isPluginEnabled('no')}{$i18n.label.configure}{/if}
{$forms.pluginsForm.locking.control} {if $user->isPluginEnabled('lk')}{$i18n.label.configure}{/if}
{$forms.pluginsForm.quotas.control} {if $user->isPluginEnabled('mq')}{$i18n.label.configure}{/if}
{$forms.pluginsForm.week_view.control} {if $user->isPluginEnabled('wv')}{$i18n.label.configure}{/if}
{$forms.pluginsForm.work_units.control} {if $user->isPluginEnabled('wu')}{$i18n.label.configure}{/if}
{$forms.pluginsForm.approval.control} {$i18n.label.what_is_it}
{$forms.pluginsForm.timesheets.control} {$i18n.label.what_is_it}
{$forms.pluginsForm.templates.control} {$i18n.label.what_is_it} {if $user->isPluginEnabled('tp')}{$i18n.label.configure}{/if}
{$forms.pluginsForm.attachments.control} {$i18n.label.what_is_it}
+
{$forms.pluginsForm.btn_save.control}
+{$forms.pluginsForm.close} diff --git a/WEB-INF/templates/predefined_expense_add.tpl b/WEB-INF/templates/predefined_expense_add.tpl index 1bb788df9..5e7473848 100644 --- a/WEB-INF/templates/predefined_expense_add.tpl +++ b/WEB-INF/templates/predefined_expense_add.tpl @@ -1,26 +1,21 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.predefinedExpenseForm.open} - +
+ - + + + + + + + + + +
- - - - - - - - - - - - - - - - - -
{$i18n.label.thing_name} (*):{$forms.predefinedExpenseForm.name.control}
{$i18n.label.cost} (*):{$forms.predefinedExpenseForm.cost.control} {$user->currency|escape}
{$i18n.label.required_fields}
 
{$forms.predefinedExpenseForm.btn_add.control}
-
{$forms.predefinedExpenseForm.name.control}
{$forms.predefinedExpenseForm.cost.control} {$user->getCurrency()|escape}
{$i18n.label.required_fields}
+
{$forms.predefinedExpenseForm.btn_add.control}
{$forms.predefinedExpenseForm.close} diff --git a/WEB-INF/templates/predefined_expense_delete.tpl b/WEB-INF/templates/predefined_expense_delete.tpl index e91692f4c..23a7193c8 100644 --- a/WEB-INF/templates/predefined_expense_delete.tpl +++ b/WEB-INF/templates/predefined_expense_delete.tpl @@ -1,18 +1,7 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.predefinedExpenseDeleteForm.open} - - - - -
- - - - - - - - - -
{$predefined_expense_to_delete|escape}
 
{$forms.predefinedExpenseDeleteForm.btn_delete.control}  {$forms.predefinedExpenseDeleteForm.btn_cancel.control}
-
+
{$predefined_expense_to_delete|escape}
+
{$forms.predefinedExpenseDeleteForm.btn_delete.control} {$forms.predefinedExpenseDeleteForm.btn_cancel.control}
{$forms.predefinedExpenseDeleteForm.close} diff --git a/WEB-INF/templates/predefined_expense_edit.tpl b/WEB-INF/templates/predefined_expense_edit.tpl index c9947aecf..06af4a315 100644 --- a/WEB-INF/templates/predefined_expense_edit.tpl +++ b/WEB-INF/templates/predefined_expense_edit.tpl @@ -1,26 +1,21 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.predefinedExpenseForm.open} - +
+ - + + + + + + + + + +
- - - - - - - - - - - - - - - - - -
{$i18n.label.thing_name} (*):{$forms.predefinedExpenseForm.name.control}
{$i18n.label.cost} (*):{$forms.predefinedExpenseForm.cost.control} {$user->currency|escape}
{$i18n.label.required_fields}
 
{$forms.predefinedExpenseForm.btn_submit.control}
-
{$forms.predefinedExpenseForm.name.control}
{$forms.predefinedExpenseForm.cost.control} {$user->getCurrency()|escape}
{$i18n.label.required_fields}
+
{$forms.predefinedExpenseForm.btn_submit.control}
{$forms.predefinedExpenseForm.close} diff --git a/WEB-INF/templates/predefined_expenses.tpl b/WEB-INF/templates/predefined_expenses.tpl index a254da390..c0d45af9e 100644 --- a/WEB-INF/templates/predefined_expenses.tpl +++ b/WEB-INF/templates/predefined_expenses.tpl @@ -1,32 +1,24 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.predefinedExpensesForm.open} - +
- + + + + +{if $predefined_expenses} + {foreach $predefined_expenses as $predefined_expense} + + + + + + + {/foreach} +{/if}
-{if $user->canManageTeam()} - - - - - - - - {if $predefined_expenses} - {foreach $predefined_expenses as $predefined_expense} - - - - - - - {/foreach} - {/if} -
{$i18n.label.thing_name}{$i18n.label.cost}{$i18n.label.edit}{$i18n.label.delete}
{$predefined_expense['name']|escape}{$predefined_expense['cost']|escape}{$i18n.label.edit}{$i18n.label.delete}
- - - -

{$forms.predefinedExpensesForm.btn_add.control}
-{/if} -
{$i18n.label.thing_name}{$i18n.label.cost}
{$predefined_expense['name']|escape}{$predefined_expense['cost']|escape}{$i18n.label.edit}{$i18n.label.delete}
+
{$forms.predefinedExpensesForm.btn_add.control}
{$forms.predefinedExpensesForm.close} diff --git a/WEB-INF/templates/profile_edit.tpl b/WEB-INF/templates/profile_edit.tpl index 8c9cb8d58..772172485 100644 --- a/WEB-INF/templates/profile_edit.tpl +++ b/WEB-INF/templates/profile_edit.tpl @@ -1,220 +1,43 @@ - +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} {$forms.profileForm.open} - -{if $user->canManageTeam()} -{include file="datetime_format_preview.tpl"} -{/if} - - - - - + + + + + + + +
- - - - - - - - - +
{$i18n.label.person_name} (*):{$forms.profileForm.name.control}
{$i18n.label.login} (*):{$forms.profileForm.login.control}
+ + + + + + + + + + + + {if !$auth_external} - - - - - - - - + + + + + + + + + + + + + {/if} - - - - - - - - - -{if $user->canManageTeam()} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {* initialize preview text *} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{/if} - - - - - - - -
{$forms.profileForm.name.control}
{$forms.profileForm.login.control}
{$i18n.label.password} (*):{$forms.profileForm.password1.control}
{$i18n.label.confirm_password} (*):{$forms.profileForm.password2.control}
{$forms.profileForm.password1.control}
{$forms.profileForm.password2.control}
{$i18n.label.email}:{$forms.profileForm.email.control}
{$i18n.label.required_fields}
 
{$i18n.label.team_name}:{$forms.profileForm.team_name.control}
{$i18n.label.currency}:{$forms.profileForm.currency.control}
{$i18n.label.language}:{$forms.profileForm.lang.control}
{$i18n.label.decimal_mark}:{$forms.profileForm.decimal_mark.control}  
{$i18n.label.date_format}:{$forms.profileForm.format_date.control}  
{$i18n.label.time_format}:{$forms.profileForm.format_time.control}  
{$i18n.label.week_start}:{$forms.profileForm.start_week.control}
{$i18n.form.profile.tracking_mode}:{$forms.profileForm.tracking_mode.control}
{$i18n.form.profile.record_type}:{$forms.profileForm.record_type.control}
{$i18n.form.profile.uncompleted_indicators}:{$forms.profileForm.uncompleted_indicators.control}
  
{$i18n.form.profile.plugins}
 
{$forms.profileForm.charts.control}
{$forms.profileForm.clients.control} {$forms.profileForm.client_required.control}
{$forms.profileForm.invoices.control}
{$forms.profileForm.custom_fields.control} {$i18n.label.configure}
{$forms.profileForm.expenses.control} {$forms.profileForm.tax_expenses.control} {$i18n.label.configure}
{$forms.profileForm.notifications.control} {$i18n.label.configure}
{$forms.profileForm.locking.control} {$i18n.label.configure}
{$forms.profileForm.quotas.control} {$i18n.label.configure}
 
{$forms.profileForm.btn_save.control}
-
{$forms.profileForm.email.control}
{$i18n.label.required_fields}
+
{$forms.profileForm.btn_save.control}
{$forms.profileForm.close} diff --git a/WEB-INF/templates/project_add.tpl b/WEB-INF/templates/project_add.tpl index 3aad22c20..7b1fd835c 100644 --- a/WEB-INF/templates/project_add.tpl +++ b/WEB-INF/templates/project_add.tpl @@ -1,42 +1,62 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.projectForm.open} - +
+ - +{if $show_users} + + + + + + + +{/if} +{if $show_tasks} + + + + + + + +{/if} + + + + +
- - - - - - - - - - - - - - - -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - - + + + + + + + + + + +{if isset($custom_fields) && $custom_fields->projectFields} + {foreach $custom_fields->projectFields as $projectField} + {assign var="control_name" value='project_field_'|cat:$projectField['id']} + + + + + + + {/foreach} +{/if} +{if $show_files} + + + + + + {/if} - - - - - - - - - - - -
{$i18n.label.thing_name} (*):{$forms.projectForm.project_name.control}
{$i18n.label.description}:{$forms.projectForm.description.control}
 
{$i18n.label.users}:{$forms.projectForm.users.control}
 
{$i18n.label.tasks}:{$forms.projectForm.tasks.control}
{$forms.projectForm.project_name.control}
{$forms.projectForm.description.control}
{$forms.projectForm.$control_name.control}
{$forms.projectForm.newfile.control}
{$i18n.label.required_fields}
 
{$forms.projectForm.btn_add.control}
-
{$i18n.label.users}:
{$i18n.label.users}:{$forms.projectForm.users.control}
{$i18n.label.tasks}:
{$i18n.label.tasks}:{$forms.projectForm.tasks.control}
{$i18n.label.required_fields}
{$forms.projectForm.btn_add.control}
{$forms.projectForm.close} diff --git a/WEB-INF/templates/project_delete.tpl b/WEB-INF/templates/project_delete.tpl index 98d3e836d..86a4cab3f 100644 --- a/WEB-INF/templates/project_delete.tpl +++ b/WEB-INF/templates/project_delete.tpl @@ -1,18 +1,7 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.projectDeleteForm.open} - - - - -
- - - - - - - - - -
{$project_to_delete|escape}
 
{$forms.projectDeleteForm.btn_delete.control}  {$forms.projectDeleteForm.btn_cancel.control}
-
+
{$project_to_delete|escape}
+
{$forms.projectDeleteForm.btn_delete.control} {$forms.projectDeleteForm.btn_cancel.control}
{$forms.projectDeleteForm.close} diff --git a/WEB-INF/templates/project_edit.tpl b/WEB-INF/templates/project_edit.tpl index 8faf675cb..4e3b02e79 100644 --- a/WEB-INF/templates/project_edit.tpl +++ b/WEB-INF/templates/project_edit.tpl @@ -1,45 +1,57 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.projectForm.open} - +
+ - +{if $show_tasks} + + + + + + +{/if} +
- - - - - - - - - - - - - - - - - - -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - - + + + + + + + + + + +{if isset($custom_fields) && $custom_fields->projectFields} + {foreach $custom_fields->projectFields as $projectField} + {assign var="control_name" value='project_field_'|cat:$projectField['id']} + + + + + + + {/foreach} +{/if} + + + + + + + +{if $show_users} + + + + + + + {/if} - - - - - - - - - - - -
{$i18n.label.thing_name} (*):{$forms.projectForm.project_name.control}
{$i18n.label.description}:{$forms.projectForm.description.control}
{$i18n.label.status}:{$forms.projectForm.status.control}
 
{$i18n.label.users}:{$forms.projectForm.users.control}
 
{$i18n.label.tasks}:{$forms.projectForm.tasks.control}
{$forms.projectForm.project_name.control}
{$forms.projectForm.description.control}
{$forms.projectForm.$control_name.control}
{$forms.projectForm.status.control}
{$i18n.label.users}:
{$i18n.label.users}:{$forms.projectForm.users.control}
{$i18n.label.required_fields}
 
{$forms.projectForm.btn_save.control} {$forms.projectForm.btn_copy.control}
-
{$i18n.label.tasks}:
{$i18n.label.tasks}:{$forms.projectForm.tasks.control}
{$i18n.label.required_fields}
+
{$forms.projectForm.btn_save.control} {$forms.projectForm.btn_copy.control}
{$forms.projectForm.close} diff --git a/WEB-INF/templates/projects.tpl b/WEB-INF/templates/projects.tpl index 1c97753e6..bb40d1430 100644 --- a/WEB-INF/templates/projects.tpl +++ b/WEB-INF/templates/projects.tpl @@ -2,83 +2,90 @@ function chLocation(newLocation) { document.location = newLocation; } - - -
-{if $user->canManageTeam()} - - {if $inactive_projects} - - {/if} - - - - - - +{if $user->can('manage_projects')} + {if $inactive_projects}
{$i18n.form.projects.active_projects}
{/if} {if $active_projects} +
{$i18n.form.projects.active_projects}
{$i18n.label.thing_name}{$i18n.label.description}{$i18n.label.edit}{$i18n.label.delete}
+ + + + {if $show_files} + + {/if} + + + {foreach $active_projects as $project} - - - - - - + + + + {if $show_files} + {if $project.has_files} + + {else} + + {/if} + {/if} + + + {/foreach} +
{$i18n.label.thing_name}{$i18n.label.description}
{$project.name|escape}{$project.description|escape}{$i18n.label.edit}{$i18n.label.delete}
{$project.name|escape}{$project.description|escape}{$i18n.label.files}{$i18n.label.files}{$i18n.label.edit}{$i18n.label.delete}
{/if} -
- - - - - -

-
-
- +
{if $inactive_projects} - - - - - - - - +
{$i18n.form.projects.inactive_projects}
+
{$i18n.form.projects.inactive_projects}
{$i18n.label.thing_name}{$i18n.label.description}{$i18n.label.edit}{$i18n.label.delete}
+ + + + {if $show_files} + + {/if} + + + {foreach $inactive_projects as $project} - - - - - - + + + + {if $show_files} + {if $project.has_files} + + {else} + + {/if} + {/if} + + + {/foreach} -
{$i18n.label.thing_name}{$i18n.label.description}
{$project.name|escape}{$project.description|escape}{$i18n.label.edit}{$i18n.label.delete}
{$project.name|escape}{$project.description|escape}{$i18n.label.files}{$i18n.label.files}{$i18n.label.edit}{$i18n.label.delete}
- - - - - -

-
-
+ +
{/if} {else} - - - - - +
{$i18n.label.thing_name}{$i18n.label.description}
+ + + + {if $show_files} + + {/if} + {if $active_projects} {foreach $active_projects as $project} - - - - + + + + {if $show_files} + {if $project.has_files} + + {else} + + {/if} + {/if} + {/foreach} {/if} -
{$i18n.label.thing_name}{$i18n.label.description}
{$project.name|escape}{$project.description|escape}
{$project.name|escape}{$project.description|escape}{$i18n.label.files} 
-{/if} - - +{/if} diff --git a/WEB-INF/templates/puncher.tpl b/WEB-INF/templates/puncher.tpl new file mode 100644 index 000000000..98bd5ca24 --- /dev/null +++ b/WEB-INF/templates/puncher.tpl @@ -0,0 +1,210 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{include file="time_script.tpl"} + +
00:00
+ + + +{if $uncompleted_today} + +{/if} + +{$forms.timeRecordForm.open} + +{if $show_client} + + + + + + +{/if} +{if $show_billable} + + + + + +{/if} +{if isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="control_name" value='time_field_'|cat:$timeField['id']} + + + + + + + {/foreach} +{/if} +{if $show_project} + + + + + + +{/if} +{if $show_task} + + + + + + +{/if} + + +
{$forms.timeRecordForm.client.control}
 
{$forms.timeRecordForm.$control_name.control}
{$forms.timeRecordForm.project.control}
{$forms.timeRecordForm.task.control}
{$i18n.label.required_fields}
+{if isset($forms.timeRecordForm.btn_start.control)}
{$forms.timeRecordForm.btn_start.control}
{/if} +{if isset($forms.timeRecordForm.btn_stop.control)}
{$forms.timeRecordForm.btn_stop.control}
{/if} +{if $time_records} +
+ + + {if $show_client} + + {/if} + {if $show_record_custom_fields && isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + + {/foreach} + {/if} + {if $show_project} + + {/if} + {if $show_task} + + {/if} + {if $show_start} + + + {/if} + + {if $show_note_column} + + {/if} + {if $show_files} + + {/if} + + + + {foreach $time_records as $record} + + {if $show_client} + + {/if} + {* record custom fields *} + {if $show_record_custom_fields && isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="control_name" value='time_field_'|cat:$timeField['id']} + + {/foreach} + {/if} + {if $show_project} + + {/if} + {if $show_task} + + {/if} + {if $show_start} + + + {/if} + + {if $show_note_column} + + {/if} + {if $show_files} + {if $record.has_files} + + {else} + + {/if} + {/if} + + + + {if $show_note_row && $record.comment} + + + + + {/if} + {/foreach} +
{$i18n.label.client}{$timeField['label']|escape}{$i18n.label.project}{$i18n.label.task}{$i18n.label.start}{$i18n.label.finish}{$i18n.label.duration}{$i18n.label.note}
{$record.client|escape}{$record.$control_name|escape}{$record.project|escape}{$record.task|escape}{if $record.start}{$record.start}{else} {/if}{if $record.finish}{$record.finish}{else} {/if}{if ($record.duration == '0:00' && $record.start <> '')}{$i18n.form.time.uncompleted}{else}{$record.duration}{/if}{if $record.comment}{$record.comment|escape}{else} {/if}{$i18n.label.files}{$i18n.label.files} + {if $record.approved || $record.timesheet_id || $record.invoice_id} +   + {else} + {$i18n.label.edit} + {/if} + + {if $record.approved || $record.timesheet_id || $record.invoice_id} +   + {else} + {$i18n.label.delete} + {/if} +
{$i18n.label.note}:{$record.comment|escape}
+
+{/if} +{$forms.timeRecordForm.close} + +
+ + + + + +
{$i18n.label.week_total}: {$week_total}{$i18n.label.day_total}: {$day_total}
+
diff --git a/WEB-INF/templates/puncher_conf.tpl b/WEB-INF/templates/puncher_conf.tpl new file mode 100644 index 000000000..9dc6169b1 --- /dev/null +++ b/WEB-INF/templates/puncher_conf.tpl @@ -0,0 +1,17 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.puncherConfigForm.open} + + + + + + + +
{$forms.puncherConfigForm.puncher_menu.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
+
{$forms.puncherConfigForm.btn_save.control}
+{$forms.puncherConfigForm.close} diff --git a/WEB-INF/templates/quotas.tpl b/WEB-INF/templates/quotas.tpl index 6ce26a913..1c38bb322 100644 --- a/WEB-INF/templates/quotas.tpl +++ b/WEB-INF/templates/quotas.tpl @@ -1,48 +1,36 @@ - - -
{$i18n.form.quota.hint}
+{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} +
{$i18n.form.quota.hint}
{$forms.monthlyQuotasForm.open} -
- - - - -
- - - - - - -
{$i18n.form.quota.workday_hours}:{$forms.monthlyQuotasForm.workdayHours.control}
-
-
- +
+ + + + + + + - - + + - + +
{$forms.monthlyQuotasForm.workdayHours.control}
{$i18n.form.quota.year}:{$forms.monthlyQuotasForm.year.control}{$forms.monthlyQuotasForm.year.control}
 
+
+ - + + + +{/foreach}
- - - - - + + + {foreach $months as $month} - - - - -{/foreach} - - -
{$i18n.form.quota.month}{$i18n.form.quota.quota}
{$i18n.form.quota.month}{$i18n.label.quota}
{$month}:{$forms.monthlyQuotasForm.$month.control}
 
-
{$month}:{$forms.monthlyQuotasForm.$month.control}
+
{$forms.monthlyQuotasForm.close} -{$forms.reportForm.open} - - - - {if $user->canManageTeam() || $user->isClient()}{/if} - {if $bean->getAttribute('chclient')}{/if} - {if $bean->getAttribute('chproject')}{/if} - {if $bean->getAttribute('chtask')}{/if} - {if $bean->getAttribute('chcf_1')}{/if} - {if $bean->getAttribute('chstart')}{/if} - {if $bean->getAttribute('chfinish')}{/if} - {if $bean->getAttribute('chduration')}{/if} - {if $bean->getAttribute('chnote')}{/if} - {if $bean->getAttribute('chcost')}{/if} - {if $bean->getAttribute('chinvoice')}{/if} - +{* normal report *} +
- - - - -
{$i18n.form.report.export} {if file_exists('WEB-INF/lib/tcpdf')}PDF,{/if} XML {$i18n.label.or} CSV
- - +
{$i18n.form.report.export} {if file_exists('WEB-INF/lib/tcpdf')}PDF,{/if} XML {$i18n.label.or} CSV
+{$forms.reportViewForm.open} +{* totals only report *} {if $bean->getAttribute('chtotalsonly')} - - - {if $bean->getAttribute('chduration')}{/if} - {if $bean->getAttribute('chcost')}{/if} - +
{$group_by_header|escape}{$i18n.label.duration}{$i18n.label.cost}
+ + + {if $bean->getAttribute('chduration')}{/if} + {if $bean->getAttribute('chunits')}{/if} + {if $bean->getAttribute('chcost')}{/if} + {foreach $subtotals as $subtotal} - - - {if $bean->getAttribute('chduration')}{/if} - {if $bean->getAttribute('chcost')}{/if} - + + + {if $bean->getAttribute('chduration')}{/if} + {if $bean->getAttribute('chunits')}{/if} + {if $bean->getAttribute('chcost')}{/if} + {/foreach} - - - - - {if $bean->getAttribute('chduration')}{/if} - {if $bean->getAttribute('chcost')}{/if} - + {* print totals *} + + + {if $bean->getAttribute('chduration')}{/if} + {if $bean->getAttribute('chunits')}{/if} + {if $bean->getAttribute('chcost')}{/if} + +
{$group_by_header|escape}{$i18n.label.duration}{$i18n.label.work_units_short}{$i18n.label.cost}
{if $subtotal['name']}{$subtotal['name']|escape}{else} {/if}{$subtotal['time']}{if $user->canManageTeam() || $user->isClient()}{$subtotal['cost']}{else}{$subtotal['expenses']}{/if}
{if $subtotal['name']}{$subtotal['name']|escape}{else} {/if}{$subtotal['time']}{$subtotal['units']}{if $user->can('manage_invoices') || $user->isClient()}{$subtotal['cost']}{else}{$subtotal['expenses']}{/if}
 
{$i18n.label.total}{$totals['time']}{$user->currency|escape} {if $user->canManageTeam() || $user->isClient()}{$totals['cost']}{else}{$totals['expenses']}{/if}
{$i18n.label.total}:{$totals['time']}{$totals['units']}{$user->currency|escape} {if $user->can('manage_invoices') || $user->isClient()}{$totals['cost']}{else}{$totals['expenses']}{/if}
{else} - -
{$i18n.label.date}{$i18n.label.user}{$i18n.label.client}{$i18n.label.project}{$i18n.label.task}{$custom_fields->fields[0]['label']|escape}{$i18n.label.start}{$i18n.label.finish}{$i18n.label.duration}{$i18n.label.note}{$i18n.label.cost}{$i18n.label.invoice}
+ + + {if $user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()}{/if} + {* user custom fields *} + {if isset($custom_fields) && $custom_fields->userFields} + {foreach $custom_fields->userFields as $userField} + {assign var="checkbox_control_name" value='show_user_field_'|cat:$userField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} + {if $bean->getAttribute('chclient')}{/if} + {if $bean->getAttribute('chproject')}{/if} + {* project custom fields *} + {if isset($custom_fields) && $custom_fields->projectFields} + {foreach $custom_fields->projectFields as $projectField} + {assign var="checkbox_control_name" value='show_project_field_'|cat:$projectField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} + {if $bean->getAttribute('chtask')}{/if} + {* time custom fields *} + {if isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="checkbox_control_name" value='show_time_field_'|cat:$timeField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} + {if $bean->getAttribute('chstart')}{/if} + {if $bean->getAttribute('chfinish')}{/if} + {if $bean->getAttribute('chduration')}{/if} + {if $bean->getAttribute('chunits')}{/if} + {if $bean->getAttribute('chnote') && !$note_on_separate_row}{/if} + {if $bean->getAttribute('chcost')} + {if $show_cost_per_hour}{/if} + + {/if} + {if $bean->getAttribute('chapproved')}{/if} + {if $bean->getAttribute('chpaid')}{/if} + {if $bean->getAttribute('chip')}{/if} + {if $bean->getAttribute('chinvoice')}{/if} + {if $bean->getAttribute('chtimesheet')}{/if} + {if $bean->getAttribute('chfiles')}{/if} + {if $use_checkboxes}{/if} + {* column for edit icons *} + {foreach $report_items as $item} - + {* print subtotal for a block of grouped values *} {$cur_date = $item.date} {if $print_subtotals} {$cur_grouped_by = $item.grouped_by} {if $cur_grouped_by != $prev_grouped_by && !$first_pass} - - {/if}{/if} - {if $bean->getAttribute('chclient')}{/if}{/if} - {if $bean->getAttribute('chproject')}{/if}{/if} - {if $bean->getAttribute('chtask')}{/if}{/if} - {if $bean->getAttribute('chcf_1')}{/if}{/if} + + + {if $user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()}{/if} + {* user custom fields *} + {if isset($custom_fields) && $custom_fields->userFields} + {foreach $custom_fields->userFields as $userField} + {assign var="checkbox_control_name" value='show_user_field_'|cat:$userField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} + {if $bean->getAttribute('chclient')}{/if} + {if $bean->getAttribute('chproject')}{/if} + {* project custom fields *} + {if isset($custom_fields) && $custom_fields->projectFields} + {foreach $custom_fields->projectFields as $projectField} + {assign var="checkbox_control_name" value='show_project_field_'|cat:$projectField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} + {if $bean->getAttribute('chtask')}{/if} + + {* time custom fields *} + {if isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="checkbox_control_name" value='show_time_field_'|cat:$timeField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} {if $bean->getAttribute('chstart')}{/if} {if $bean->getAttribute('chfinish')}{/if} - {if $bean->getAttribute('chduration')}{/if} - {if $bean->getAttribute('chnote')}{/if} - {if $bean->getAttribute('chcost')}{/if} + {if $bean->getAttribute('chduration')}{/if} + {if $bean->getAttribute('chunits')}{/if} + {if $bean->getAttribute('chnote') && !$note_on_separate_row}{/if} + {if $bean->getAttribute('chcost')} + {if $show_cost_per_hour}{/if} + + {/if} + {if $bean->getAttribute('chapproved')}{/if} + {if $bean->getAttribute('chpaid')}{/if} + {if $bean->getAttribute('chip')}{/if} {if $bean->getAttribute('chinvoice')}{/if} - - + {if $bean->getAttribute('chtimesheet')}{/if} + {if $bean->getAttribute('chfiles')}{/if} + {if $use_checkboxes}{/if} + {* column for edit icons *} + + {/if} {$first_pass = false} {/if} - - {if $cur_date != $prev_date} - {if $report_row_class == 'rowReportItem'} {$report_row_class = 'rowReportItemAlt'} {else} {$report_row_class = 'rowReportItem'} {/if} - {/if} - - - {if $user->canManageTeam() || $user->isClient()}{/if} - {if $bean->getAttribute('chclient')}{/if} - {if $bean->getAttribute('chproject')}{/if} - {if $bean->getAttribute('chtask')}{/if} - {if $bean->getAttribute('chcf_1')}{/if} - {if $bean->getAttribute('chstart')}{/if} - {if $bean->getAttribute('chfinish')}{/if} - {if $bean->getAttribute('chduration')}{/if} - {if $bean->getAttribute('chnote')}{/if} - {if $bean->getAttribute('chcost')}{/if} - {if $bean->getAttribute('chinvoice')} - - {if $use_checkboxes} - {if 1 == $item.type}{/if} - {if 2 == $item.type}{/if} - {/if} + {* print regular row *} + + + {if $user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()}{/if} + {* user custom fields *} + {if isset($custom_fields) && $custom_fields->userFields} + {foreach $custom_fields->userFields as $userField} + {assign var="control_name" value='user_field_'|cat:$userField['id']} + {assign var="checkbox_control_name" value='show_user_field_'|cat:$userField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} + {if $bean->getAttribute('chclient')}{/if} + {if $bean->getAttribute('chproject')}{/if} + {* project custom fields *} + {if isset($custom_fields) && $custom_fields->projectFields} + {foreach $custom_fields->projectFields as $projectField} + {assign var="control_name" value='project_field_'|cat:$projectField['id']} + {assign var="checkbox_control_name" value='show_project_field_'|cat:$projectField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} + {if $bean->getAttribute('chtask')}{/if} + {* time custom fields *} + {if isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="control_name" value='time_field_'|cat:$timeField['id']} + {assign var="checkbox_control_name" value='show_time_field_'|cat:$timeField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} + {if $bean->getAttribute('chstart')}{/if} + {if $bean->getAttribute('chfinish')}{/if} + {if $bean->getAttribute('chduration')}{/if} + {if $bean->getAttribute('chunits')}{/if} + {if $bean->getAttribute('chnote') && !$note_on_separate_row}{/if} + {if $bean->getAttribute('chcost')} + {if $show_cost_per_hour}{/if} + + {/if} + {if $bean->getAttribute('chapproved')}{/if} + {if $bean->getAttribute('chpaid')}{/if} + {if $bean->getAttribute('chip')}{/if} + {if $bean->getAttribute('chinvoice')}{/if} + {if $bean->getAttribute('chtimesheet')}{/if} + {if $bean->getAttribute('chfiles')} + {if 1 == $item.type}{/if} + {if 2 == $item.type}{/if} + {/if} + {if $use_checkboxes} + {if 1 == $item.type}{/if} + {if 2 == $item.type}{/if} + {/if} + {if $item.approved || $item.timesheet_id || $item.invoice_id} + + {else} + {if 1 == $item.type}{/if} + {if 2 == $item.type}{/if} + {/if} + + {if $note_on_separate_row && $bean->getAttribute('chnote') && $item.note} + + + + {/if} - {$prev_date = $item.date} {if $print_subtotals} {$prev_grouped_by = $item.grouped_by} {/if} {/foreach} - + {* print a terminating subtotal *} {if $print_subtotals} - - {/if}{/if} - {if $bean->getAttribute('chclient')}{/if}{/if} - {if $bean->getAttribute('chproject')}{/if}{/if} - {if $bean->getAttribute('chtask')}{/if}{/if} - {if $bean->getAttribute('chcf_1')}{/if}{/if} + + + {if $user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()}{/if} + + {* user custom fields *} + {if isset($custom_fields) && $custom_fields->userFields} + {foreach $custom_fields->userFields as $userField} + {assign var="checkbox_control_name" value='show_user_field_'|cat:$userField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} + {if $bean->getAttribute('chclient')}{/if} + {if $bean->getAttribute('chproject')}{/if} + {* project custom fields *} + {if isset($custom_fields) && $custom_fields->projectFields} + {foreach $custom_fields->projectFields as $projectField} + {assign var="checkbox_control_name" value='show_project_field_'|cat:$projectField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} + {if $bean->getAttribute('chtask')}{/if} + {* time custom fields *} + {if isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="checkbox_control_name" value='show_time_field_'|cat:$timeField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} {if $bean->getAttribute('chstart')}{/if} {if $bean->getAttribute('chfinish')}{/if} - {if $bean->getAttribute('chduration')}{/if} - {if $bean->getAttribute('chnote')}{/if} - {if $bean->getAttribute('chcost')}{/if} + {if $bean->getAttribute('chduration')}{/if} + {if $bean->getAttribute('chunits')}{/if} + {if $bean->getAttribute('chnote') && !$note_on_separate_row}{/if} + {if $bean->getAttribute('chcost')} + {if $show_cost_per_hour}{/if} + + {/if} + {if $bean->getAttribute('chapproved')}{/if} + {if $bean->getAttribute('chpaid')}{/if} + {if $bean->getAttribute('chip')}{/if} {if $bean->getAttribute('chinvoice')}{/if} - + {if $bean->getAttribute('chtimesheet')}{/if} + {if $bean->getAttribute('chfiles')}{/if} + {if $use_checkboxes}{/if} + {* column for edit icons *} + {/if} - - - - - {if $user->canManageTeam() || $user->isClient()}{/if} + {* print totals *} + + + + {if $user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()}{/if} + {* user custom fields *} + {if isset($custom_fields) && $custom_fields->userFields} + {foreach $custom_fields->userFields as $userField} + {assign var="checkbox_control_name" value='show_user_field_'|cat:$userField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} {if $bean->getAttribute('chclient')}{/if} {if $bean->getAttribute('chproject')}{/if} + {* project custom fields *} + {if isset($custom_fields) && $custom_fields->projectFields} + {foreach $custom_fields->projectFields as $projectField} + {assign var="checkbox_control_name" value='show_project_field_'|cat:$projectField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} {if $bean->getAttribute('chtask')}{/if} - {if $bean->getAttribute('chcf_1')}{/if} + {* time custom fields *} + {if isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="checkbox_control_name" value='show_time_field_'|cat:$timeField['id']} + {if $bean->getAttribute($checkbox_control_name)}{/if} + {/foreach} + {/if} {if $bean->getAttribute('chstart')}{/if} {if $bean->getAttribute('chfinish')}{/if} - {if $bean->getAttribute('chduration')}{/if} - {if $bean->getAttribute('chnote')}{/if} - {if $bean->getAttribute('chcost')}{/if} + {if $bean->getAttribute('chduration')}{/if} + {if $bean->getAttribute('chunits')}{/if} + {if $bean->getAttribute('chnote') && !$note_on_separate_row}{/if} + {if $bean->getAttribute('chcost')} + {if $show_cost_per_hour}{/if} + + {/if} + {if $bean->getAttribute('chapproved')}{/if} + {if $bean->getAttribute('chpaid')}{/if} + {if $bean->getAttribute('chip')}{/if} {if $bean->getAttribute('chinvoice')}{/if} - -{/if} -
{$i18n.label.date}{$i18n.label.user}{{$userField['label']|escape}}{$i18n.label.client}{$i18n.label.project}{{$projectField['label']|escape}}{$i18n.label.task}{{$timeField['label']|escape}}{$i18n.label.start}{$i18n.label.finish}{$i18n.label.duration}{$i18n.label.work_units_short}{$i18n.label.note}{$i18n.form.report.per_hour}{$i18n.label.cost}{$i18n.label.approved}{$i18n.label.paid}{$i18n.label.ip}{$i18n.label.invoice}{$i18n.label.timesheet}
{$i18n.label.subtotal} - {if $user->canManageTeam() || $user->isClient()}{if $group_by == 'user'}{$subtotals[$prev_grouped_by]['name']|escape}{if $group_by == 'client'}{$subtotals[$prev_grouped_by]['name']|escape}{if $group_by == 'project'}{$subtotals[$prev_grouped_by]['name']|escape}{if $group_by == 'task'}{$subtotals[$prev_grouped_by]['name']|escape}{if $group_by == 'cf_1'}{$subtotals[$prev_grouped_by]['name']|escape}
{$i18n.label.subtotal}{$subtotals[$prev_grouped_by]['user']|escape}{$subtotals[$prev_grouped_by]['client']|escape}{$subtotals[$prev_grouped_by]['project']|escape}{$subtotals[$prev_grouped_by]['task']|escape}{$subtotals[$prev_grouped_by]['time']}{if $user->canManageTeam() || $user->isClient()}{$subtotals[$prev_grouped_by]['cost']}{else}{$subtotals[$prev_grouped_by]['expenses']}{/if}{$subtotals[$prev_grouped_by]['time']}{$subtotals[$prev_grouped_by]['units']}{if $user->can('manage_invoices') || $user->isClient()}{$subtotals[$prev_grouped_by]['cost']}{else}{$subtotals[$prev_grouped_by]['expenses']}{/if}
 
 
{$item.date}{$item.user|escape}{$item.client|escape}{$item.project|escape}{$item.task|escape}{$item.cf_1|escape}{$item.start}{$item.finish}{$item.duration}{$item.note|escape}{if $user->canManageTeam() || $user->isClient()}{$item.cost}{else}{$item.expense}{/if}{$item.invoice|escape}
{$item.date}{$item.user|escape}{$item.$control_name|escape}{$item.client|escape}{$item.project|escape}{$item.$control_name|escape}{$item.task|escape}{$item.$control_name|escape}{$item.start}{$item.finish}{$item.duration}{$item.units}{$item.note|escape}{if $user->can('manage_invoices') || $user->isClient()}{$item.cost_per_hour}{/if}{if $user->can('manage_invoices') || $user->isClient()}{$item.cost}{else}{$item.expense}{/if}{if $item.approved == 1}{$i18n.label.yes}{else}{$i18n.label.no}{/if}{if $item.paid == 1}{$i18n.label.yes}{else}{$i18n.label.no}{/if}{if $item.modified}{$item.modified_ip} {$item.modified}{else}{$item.created_ip} {$item.created}{/if}{$item.invoice|escape}{$item.timesheet_name|escape}{if $item.has_files}{$i18n.label.files}{/if}{if $item.has_files}{$i18n.label.files}{/if} {$i18n.label.edit}{$i18n.label.edit}
{$i18n.label.note}{$item.note|escape}
{$i18n.label.subtotal} - {if $user->canManageTeam() || $user->isClient()}{if $group_by == 'user'}{$subtotals[$cur_grouped_by]['name']|escape}{if $group_by == 'client'}{$subtotals[$cur_grouped_by]['name']|escape}{if $group_by == 'project'}{$subtotals[$cur_grouped_by]['name']|escape}{if $group_by == 'task'}{$subtotals[$cur_grouped_by]['name']|escape}{if $group_by == 'cf_1'}{$subtotals[$cur_grouped_by]['name']|escape}
{$i18n.label.subtotal}{$subtotals[$cur_grouped_by]['user']|escape}{$subtotals[$cur_grouped_by]['client']|escape}{$subtotals[$cur_grouped_by]['project']|escape}{$subtotals[$cur_grouped_by]['task']|escape}{$subtotals[$cur_grouped_by]['time']}{if $user->canManageTeam() || $user->isClient()}{$subtotals[$cur_grouped_by]['cost']}{else}{$subtotals[$cur_grouped_by]['expenses']}{/if}{$subtotals[$cur_grouped_by]['time']}{$subtotals[$cur_grouped_by]['units']}{if $user->can('manage_invoices') || $user->isClient()}{$subtotals[$cur_grouped_by]['cost']}{else}{$subtotals[$cur_grouped_by]['expenses']}{/if}
 
{$i18n.label.total}
 
{$i18n.label.total}{$totals['time']}{$user->currency|escape} {if $user->canManageTeam() || $user->isClient()}{$totals['cost']}{else}{$totals['expenses']}{/if}{$totals['time']}{$totals['units']}{$user->currency|escape} {if $user->can('manage_invoices') || $user->isClient()}{$totals['cost']}{else}{$totals['expenses']}{/if}
- - - -{if $use_checkboxes && $report_items} - - - + {if $bean->getAttribute('chtimesheet')}{/if} + {if $bean->getAttribute('chfiles')}{/if} + {if $use_checkboxes}{/if} + {* column for edit icons *}
- - -
{$forms.reportForm.recent_invoice.control} {$forms.reportForm.btn_submit.control}
-
{/if} -{$forms.reportForm.close} - - - - - + + {/if} + {if $use_assign_to_timesheet} + + + + + + + {/if}
- +{if $report_items && ($use_mark_approved || $use_mark_paid || $use_assign_to_invoice || $use_assign_to_timesheet)} +
+
+ {if $use_mark_approved} + + + + + + + {/if} + {if $use_mark_paid} + + + + + + + {/if} + {if $use_assign_to_invoice} + - + + -
{$forms.reportViewForm.mark_approved_select_options.control}
{$forms.reportViewForm.mark_approved_action_options.control} {$forms.reportViewForm.btn_mark_approved.control}
{$forms.reportViewForm.mark_paid_select_options.control}
{$forms.reportViewForm.mark_paid_action_options.control} {$forms.reportViewForm.btn_mark_paid.control}
{$forms.reportViewForm.assign_invoice_select_options.control}
{$forms.reportViewForm.recent_invoice.control} {$forms.reportViewForm.btn_assign_invoice.control}
-
{$forms.reportViewForm.assign_timesheet_select_options.control}
{$forms.reportViewForm.timesheet.control} {$forms.reportViewForm.btn_assign_timesheet.control}
+{/if} +{$forms.reportViewForm.close} +
diff --git a/WEB-INF/templates/reports.tpl b/WEB-INF/templates/reports.tpl index 5ca7a171f..485cfc121 100644 --- a/WEB-INF/templates/reports.tpl +++ b/WEB-INF/templates/reports.tpl @@ -1,4 +1,31 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.reportForm.open} -
- - - - -
- - - - - -
{$i18n.label.fav_report}:{$forms.reportForm.favorite_report.control}{$forms.reportForm.btn_generate.control} {$forms.reportForm.btn_delete.control}
-
-
- - +
+ + + + +
{$i18n.label.fav_report}:
{$forms.reportForm.favorite_report.control}
{$forms.reportForm.btn_generate.control} {$forms.reportForm.btn_delete.control}
+
+ + + + + + + + + + + + + + + + + + + + + +
{$i18n.form.reports.select_period}
{$forms.reportForm.period.control}
{$i18n.form.reports.set_period}
{$forms.reportForm.start_date.control}
{$forms.reportForm.end_date.control}
+
+{if $show_active_users} +
{if $show_inactive_users}{$i18n.label.active_users}{else}{$i18n.label.users}{/if}
+ + +
{$forms.reportForm.users_active.control}
+
+{/if} +{if $show_inactive_users} +
{$i18n.label.inactive_users}
+ + +
{$forms.reportForm.users_inactive.control}
+
+{/if} + +{if $show_client} + - + + + + +
- -{if (($user->isPluginEnabled('cl') && !($user->isClient() && $user->client_id)) || ($custom_fields && $custom_fields->fields[0] && $custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN))} - - {if $user->isPluginEnabled('cl') && !($user->isClient() && $user->client_id)}{else}{/if} - - {if ($custom_fields && $custom_fields->fields[0] && $custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)}{else}{/if} - - - - - - + + + + {/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - {/if} - +{if $show_billable} + + + + + + {/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - {/if} - +{if $show_invoice_dropdown} + + + + + + {/if} -{if $user->isPluginEnabled('iv')} - - - - - - - - - - +{if $show_paid_status} + + + + + + {/if} -{if $user->canManageTeam() || $user->isClient()} - - - - - - +{if $show_project} + + + + + + {/if} - - - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.client}  {$i18n.label.option} 
{$forms.reportForm.client.control} {$forms.reportForm.option.control}
{$forms.reportForm.client.control}
{$i18n.label.project} {$i18n.label.task}
{$forms.reportForm.include_records.control}
{$forms.reportForm.project.control} {$forms.reportForm.task.control}
{$forms.reportForm.invoice.control}
{$i18n.form.time.billable} {$i18n.label.invoice}
{$forms.reportForm.include_records.control} {$forms.reportForm.invoice.control}
{$forms.reportForm.paid_status.control}
{$i18n.label.users}
{$forms.reportForm.users.control}
{$forms.reportForm.project.control}
{$i18n.form.reports.select_period} {$i18n.form.reports.set_period}
{$forms.reportForm.period.control}{$i18n.label.start_date}:{$forms.reportForm.start_date.control}
{$i18n.label.end_date}:{$forms.reportForm.end_date.control}
{$i18n.form.reports.show_fields}
- -{if $user->isPluginEnabled('cl') || $user->isPluginEnabled('iv')} - - {if $user->isPluginEnabled('cl')} - - {/if} - {if ($user->canManageTeam() || $user->isClient()) && $user->isPluginEnabled('iv')} - - {/if} - +{if $show_task} + + + + + + {/if} - - - - -{if ((($user->canManageTeam() || $user->isClient()) || $user->isPluginEnabled('ex')) && defined('COST_ON_REPORTS') && isTrue($smarty.const.COST_ON_REPORTS))} - -{else} - +{if $show_approved} + + + + + + {/if} - - - - - -{if ($custom_fields && $custom_fields->fields[0])} - -{else} - +{if $show_timesheet_dropdown} + + + + + + {/if} - -
{$forms.reportForm.task.control}
{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}{/if}{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}{/if}
{$forms.reportForm.approved.control}
{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)}{/if}{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))}{/if}
{$forms.reportForm.timesheet.control}
-
{$i18n.form.reports.group_by}
{$forms.reportForm.group_by.control}
- -
- - - - -
- - - - - -
{$i18n.form.reports.save_as_favorite}:{$forms.reportForm.new_fav_report.control}{$forms.reportForm.btn_save.control}
-
-
- - - -
{$forms.reportForm.btn_generate.control}
+
{$forms.reportForm.note_containing.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it}
+
+ + +{if $show_client} + + + + + + +{/if} +{if $show_project} + + + + + + +{/if} +{if $show_task} + + + + + + +{/if} +{if $show_start} + + + + + + +{/if} +{if $show_finish} + + + + + + +{/if} + + + + + + + + + + + + +{if $show_work_units} + + + + + + +{/if} + + + + + + +{if $show_approved} + + + + + + +{/if} +{if $show_paid_status} + + + + + + +{/if} +{if $show_ip} + + + + + + +{/if} +{if $show_invoice_checkbox} + + + + + + +{/if} +{if $show_timesheet_checkbox} + + + + + + +{/if} +{if $show_files} + + + + + + +{/if} +
{$i18n.form.reports.show_fields}
{$forms.reportForm.chclient.control}
{$forms.reportForm.chproject.control}
{$forms.reportForm.chtask.control}
{$forms.reportForm.chstart.control}
{$forms.reportForm.chfinish.control}
{$forms.reportForm.chduration.control}
{$forms.reportForm.chnote.control}
{$forms.reportForm.chunits.control}
{$forms.reportForm.chcost.control}
{$forms.reportForm.chapproved.control}
{$forms.reportForm.chpaid.control}
{$forms.reportForm.chip.control}
{$forms.reportForm.chinvoice.control}
{$forms.reportForm.chtimesheet.control}
{$forms.reportForm.chfiles.control}
+
+ +{if isset($custom_fields) && $custom_fields->timeFields} + + {foreach $custom_fields->timeFields as $timeField} + {assign var="control_name" value='time_field_'|cat:$timeField['id']} + {assign var="checkbox_control_name" value='show_time_field_'|cat:$timeField['id']} + + + + {assign var="control_name" value='time_field_'|cat:$timeField['id']} + {assign var="checkbox_control_name" value='show_time_field_'|cat:$timeField['id']} + + + + {/foreach} +{/if} +{if isset($custom_fields) && $custom_fields->userFields} + + {foreach $custom_fields->userFields as $userField} + {assign var="control_name" value='user_field_'|cat:$userField['id']} + {assign var="checkbox_control_name" value='show_user_field_'|cat:$userField['id']} + + + + + + + {/foreach} +{/if} +{if $show_project && isset($custom_fields) && $custom_fields->projectFields} + + {foreach $custom_fields->projectFields as $projectField} + {assign var="control_name" value='project_field_'|cat:$projectField['id']} + {assign var="checkbox_control_name" value='show_project_field_'|cat:$projectField['id']} + + + + + + + {/foreach} +{/if} +
{$i18n.form.reports.time_fields}
{$forms.reportForm.$control_name.control} {$forms.reportForm.$checkbox_control_name.control}
{$i18n.form.reports.user_fields}
{$forms.reportForm.$control_name.control} {$forms.reportForm.$checkbox_control_name.control}
{$i18n.form.reports.project_fields}
{$forms.reportForm.$control_name.control} {$forms.reportForm.$checkbox_control_name.control}
+
+ + + + + + +
{$i18n.form.reports.group_by}
{$forms.reportForm.group_by1.control}
{$forms.reportForm.group_by2.control}
{$forms.reportForm.group_by3.control}
+
+ + + +
{$forms.reportForm.new_fav_report.control} {$forms.reportForm.btn_save.control}
+
{$forms.reportForm.btn_generate.control}
{$forms.reportForm.close} diff --git a/WEB-INF/templates/role_add.tpl b/WEB-INF/templates/role_add.tpl new file mode 100644 index 000000000..3fb24fee0 --- /dev/null +++ b/WEB-INF/templates/role_add.tpl @@ -0,0 +1,31 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.roleForm.open} + + + + + + + + + + + + + + + + + + + + + +
{$forms.roleForm.name.control}
{$forms.roleForm.description.control}
{$forms.roleForm.rank.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$i18n.label.required_fields}
+
{$forms.roleForm.btn_submit.control}
+{$forms.roleForm.close} diff --git a/WEB-INF/templates/role_delete.tpl b/WEB-INF/templates/role_delete.tpl new file mode 100644 index 000000000..56c5dfa07 --- /dev/null +++ b/WEB-INF/templates/role_delete.tpl @@ -0,0 +1,7 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.roleDeleteForm.open} +
{$role_to_delete|escape}
+
{$forms.roleDeleteForm.btn_delete.control} {$forms.roleDeleteForm.btn_cancel.control}
+{$forms.roleDeleteForm.close} diff --git a/WEB-INF/templates/role_edit.tpl b/WEB-INF/templates/role_edit.tpl new file mode 100644 index 000000000..096716e29 --- /dev/null +++ b/WEB-INF/templates/role_edit.tpl @@ -0,0 +1,47 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.roleForm.open} + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{$forms.roleForm.name.control}
{$forms.roleForm.description.control}
{$forms.roleForm.rank.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.roleForm.status.control}
{$i18n.label.required_fields}
+
{$forms.roleForm.btn_save.control}
+ + + + + + + + + +
{$i18n.form.roles.assigned}:
{$forms.roleForm.assigned_rights.control}
{$forms.roleForm.btn_delete.control}
{$i18n.form.roles.not_assigned}:
{$forms.roleForm.available_rights.control}
{$forms.roleForm.btn_add.control}
+{$forms.roleForm.close} diff --git a/WEB-INF/templates/roles.tpl b/WEB-INF/templates/roles.tpl new file mode 100644 index 000000000..60985c520 --- /dev/null +++ b/WEB-INF/templates/roles.tpl @@ -0,0 +1,56 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + + + +{if $inactive_roles} +
{$i18n.form.roles.active_roles}
+{/if} + + + + + + + + + {if $active_roles} + {foreach $active_roles as $role} + + + + + + + + {/foreach} + {/if} +
{$i18n.label.thing_name}{$i18n.form.roles.rank}{$i18n.label.description}
{$role.name|escape}{$role.rank}{$role.description|escape}{$i18n.label.edit}{$i18n.label.delete}
+
+ +{if $inactive_roles} +
{$i18n.form.roles.inactive_roles}
+ + + + + + + + + {if $active_roles} + {foreach $inactive_roles as $role} + + + + + + + + {/foreach} + {/if} +
{$i18n.label.thing_name}{$i18n.form.roles.rank}{$i18n.label.description}
{$role.name|escape}{$role.rank}{$role.description|escape}{$i18n.label.edit}{$i18n.label.delete}
+
+{/if} diff --git a/WEB-INF/templates/site_map.tpl b/WEB-INF/templates/site_map.tpl new file mode 100644 index 000000000..8688f7677 --- /dev/null +++ b/WEB-INF/templates/site_map.tpl @@ -0,0 +1,90 @@ +{if $authenticated} + {if $user->can('administer_site')} + {* sub menu for admin *} + + {* end of sub menu for admin *} + {* main menu for admin *} + + {* end of main menu for admin *} + {else} + {* sub menu for authorized user *} +
+ {if $user->exists() && ($user->can('track_own_time') || $user->can('track_time'))} + + {if $user->isPluginEnabled('pu') && $user->isOptionEnabled('puncher_menu')} + + {/if} + {if $user->isPluginEnabled('wv') && $user->isOptionEnabled('week_menu')} + + {/if} + {/if} + {if $user->exists() && $user->isPluginEnabled('ex') && ($user->can('track_own_expenses') || $user->can('track_expenses'))} + + {/if} + {if $user->exists() && ($user->can('view_own_reports') || $user->can('view_reports') || $user->can('view_all_reports') || $user->can('view_client_reports'))} + + {/if} + {if $user->exists() && $user->isPluginEnabled('ts') && ($user->can('track_own_time') || $user->can('track_time'))} + + {/if} + {if $user->exists() && $user->isPluginEnabled('iv') && ($user->can('manage_invoices') || $user->can('view_client_invoices'))} + + {/if} + {if ($user->exists() && $user->isPluginEnabled('ch') && ($user->can('view_own_charts') || $user->can('view_charts') || $user->can('view_all_charts'))) && + (constant('MODE_PROJECTS') == $user->getTrackingMode() || constant('MODE_PROJECTS_AND_TASKS') == $user->getTrackingMode() || + $user->isPluginEnabled('cl'))} + + {/if} + {if ($user->can('view_own_projects') || $user->can('manage_projects')) && (constant('MODE_PROJECTS') == $user->getTrackingMode() || constant('MODE_PROJECTS_AND_TASKS') == $user->getTrackingMode())} + + {/if} + {if (constant('MODE_PROJECTS_AND_TASKS') == $user->getTrackingMode() && ($user->can('view_own_tasks') || $user->can('manage_tasks')))} + + {/if} + {if $user->can('view_users') || $user->can('manage_users')} + + {/if} + {if $user->isPluginEnabled('cl') && ($user->can('view_own_clients') || $user->can('manage_clients'))} + + {/if} + {if $user->can('export_data')} + + {/if} +
+ {* end of sub menu for authorized user *} + {* main menu for authorized user *} +
+ + {if $user->exists() && $user->can('manage_own_settings')} + + {/if} + {if $user->can('manage_basic_settings')} + + {/if} + {if $user->can('manage_features')} + + {/if} + + +
+ {* end of main menu for authorized user *} + {/if} +{else} + {* main menu for non authorized user *} +
+ + {if isTrue('MULTIORG_MODE') && constant('AUTH_MODULE') == 'db'} + + {/if} + + +
+ {* end of main menu for non authorized user *} +{/if} diff --git a/WEB-INF/templates/success.tpl b/WEB-INF/templates/success.tpl new file mode 100644 index 000000000..c341a4039 --- /dev/null +++ b/WEB-INF/templates/success.tpl @@ -0,0 +1 @@ + diff --git a/WEB-INF/templates/swap_roles.tpl b/WEB-INF/templates/swap_roles.tpl new file mode 100644 index 000000000..a858db84b --- /dev/null +++ b/WEB-INF/templates/swap_roles.tpl @@ -0,0 +1,15 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +
{$i18n.form.swap.hint}
+{$forms.swapForm.open} + + + + + + + +
{$forms.swapForm.swap_with.control}
+
{$forms.swapForm.btn_submit.control} {$forms.swapForm.btn_cancel.control}
+{$forms.swapForm.close} diff --git a/WEB-INF/templates/task_add.tpl b/WEB-INF/templates/task_add.tpl index 53ce483d9..d933160af 100644 --- a/WEB-INF/templates/task_add.tpl +++ b/WEB-INF/templates/task_add.tpl @@ -1,33 +1,34 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.taskForm.open} - +
+ - + + + + + + + + + +{if $show_projects} + + + + + + + +{/if} + + + + + +
- - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.thing_name} (*):{$forms.taskForm.name.control}
{$i18n.label.description}:{$forms.taskForm.description.control}
{$i18n.label.projects}:{$forms.taskForm.projects.control}
{$i18n.label.required_fields}
 
{$forms.taskForm.btn_submit.control}
-
{$forms.taskForm.name.control}
{$forms.taskForm.description.control}
{$i18n.label.projects}:
{$i18n.label.projects}:{$forms.taskForm.projects.control}
{$i18n.label.required_fields}
{$forms.taskForm.btn_submit.control}
{$forms.taskForm.close} diff --git a/WEB-INF/templates/task_delete.tpl b/WEB-INF/templates/task_delete.tpl index 9f16d7c3a..88a33966c 100644 --- a/WEB-INF/templates/task_delete.tpl +++ b/WEB-INF/templates/task_delete.tpl @@ -1,20 +1,7 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.taskDeleteForm.open} - - - - -
- - - - - - - - - - - -
{$task_to_delete|escape}
 
{$forms.taskDeleteForm.btn_delete.control}  {$forms.taskDeleteForm.btn_cancel.control}
-
+
{$task_to_delete|escape}
+
{$forms.taskDeleteForm.btn_delete.control} {$forms.taskDeleteForm.btn_cancel.control}
{$forms.taskDeleteForm.close} diff --git a/WEB-INF/templates/task_edit.tpl b/WEB-INF/templates/task_edit.tpl index 5af8d9355..c6e433b73 100644 --- a/WEB-INF/templates/task_edit.tpl +++ b/WEB-INF/templates/task_edit.tpl @@ -1,37 +1,37 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.taskForm.open} - +
+ - + + + + + + + + + + + + + + + +{if $show_projects} + + + + + + + +{/if} + +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.thing_name} (*):{$forms.taskForm.name.control}
{$i18n.label.description}:{$forms.taskForm.description.control}
{$i18n.label.status}:{$forms.taskForm.status.control}
{$i18n.label.projects}:{$forms.taskForm.projects.control}
{$i18n.label.required_fields}
 
{$forms.taskForm.btn_save.control} {$forms.taskForm.btn_copy.control}
-
{$forms.taskForm.name.control}
{$forms.taskForm.description.control}
{$forms.taskForm.status.control}
{$i18n.label.projects}:
{$i18n.label.projects}:{$forms.taskForm.projects.control}
{$i18n.label.required_fields}
+
{$forms.taskForm.btn_save.control} {$forms.taskForm.btn_copy.control}
{$forms.taskForm.close} diff --git a/WEB-INF/templates/tasks.tpl b/WEB-INF/templates/tasks.tpl index 23d5ec4c5..6bf29d418 100644 --- a/WEB-INF/templates/tasks.tpl +++ b/WEB-INF/templates/tasks.tpl @@ -1,84 +1,75 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + - +{$forms.tasksForm.open} +{if $user->can('manage_tasks')} +
+
-
-{if $user->canManageTeam()} - - {if $inactive_tasks} - - {/if} - - - - - - + + + + +
{$i18n.form.tasks.active_tasks}
{$i18n.label.thing_name}{$i18n.label.description}{$i18n.label.edit}{$i18n.label.delete}
{$forms.tasksForm.task_required.control} {$i18n.label.what_is_it}{$forms.tasksForm.btn_save.control}
+ + {if $inactive_tasks}
{$i18n.form.tasks.active_tasks}
{/if} {if $active_tasks} + + + + + + + {foreach $active_tasks as $task} - - - - - - + + + + + + {/foreach} +
{$i18n.label.thing_name}{$i18n.label.description}
{$task.name|escape}{$task.description|escape}{$i18n.label.edit}{$i18n.label.delete}
{$task.name|escape}{$task.description|escape}{$i18n.label.edit}{$i18n.label.delete}
{/if} -
- - - - - -

-
-
- +
{if $inactive_tasks} - - - - - - - - +
{$i18n.form.tasks.inactive_tasks}
+
{$i18n.form.tasks.inactive_tasks}
{$i18n.label.thing_name}{$i18n.label.description}{$i18n.label.edit}{$i18n.label.delete}
+ + + + + + {foreach $inactive_tasks as $task} - - - - - - + + + + + + {/foreach} -
{$i18n.label.thing_name}{$i18n.label.description}
{$task.name|escape}{$task.description|escape}{$i18n.label.edit}{$i18n.label.delete}
{$task.name|escape}{$task.description|escape}{$i18n.label.edit}{$i18n.label.delete}
- - - - - -

-
-
+ +
{/if} {else} - - - - - +
{$i18n.label.thing_name}{$i18n.label.description}
+ + + + {if $active_tasks} {foreach $active_tasks as $task} - - - - + + + + {/foreach} {/if} -
{$i18n.label.thing_name}{$i18n.label.description}
{$task.name|escape}{$task.description|escape}
{$task.name|escape}{$task.description|escape}
- {/if} - - +{/if} +{$forms.tasksForm.close} diff --git a/WEB-INF/templates/template_add.tpl b/WEB-INF/templates/template_add.tpl new file mode 100644 index 000000000..554267b20 --- /dev/null +++ b/WEB-INF/templates/template_add.tpl @@ -0,0 +1,37 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.templateForm.open} + + + + + + + + + + + + + + + + + + + +{if $show_projects} + + + + + + + +{/if} + + +
{$forms.templateForm.name.control}
{$forms.templateForm.description.control}
{$forms.templateForm.content.control}
{$forms.templateForm.projects.control}
{$i18n.label.required_fields}
+
{$forms.templateForm.btn_add.control}
+{$forms.templateForm.close} diff --git a/WEB-INF/templates/template_delete.tpl b/WEB-INF/templates/template_delete.tpl new file mode 100644 index 000000000..a7ca2e75f --- /dev/null +++ b/WEB-INF/templates/template_delete.tpl @@ -0,0 +1,7 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.templateDeleteForm.open} +
{$template_to_delete|escape}
+
{$forms.templateDeleteForm.btn_delete.control} {$forms.templateDeleteForm.btn_cancel.control}
+{$forms.templateDeleteForm.close} diff --git a/WEB-INF/templates/template_edit.tpl b/WEB-INF/templates/template_edit.tpl new file mode 100644 index 000000000..bdcbb279f --- /dev/null +++ b/WEB-INF/templates/template_edit.tpl @@ -0,0 +1,43 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.templateForm.open} + + + + + + + + + + + + + + + + + + + + + + + + + +{if $show_projects} + + + + + + + +{/if} + + +
{$forms.templateForm.name.control}
{$forms.templateForm.description.control}
{$forms.templateForm.content.control}
{$forms.templateForm.status.control}
{$forms.templateForm.projects.control}
{$i18n.label.required_fields}
+
{$forms.templateForm.btn_submit.control}
+{$forms.templateForm.close} diff --git a/WEB-INF/templates/templates.tpl b/WEB-INF/templates/templates.tpl new file mode 100644 index 000000000..b37de3051 --- /dev/null +++ b/WEB-INF/templates/templates.tpl @@ -0,0 +1,69 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.templatesForm.open} +{if $inactive_templates} +
{$i18n.form.templates.active_templates}
+{/if} + + + + + + + + {foreach $active_templates as $template} + + + + + + + {/foreach} +
{$i18n.label.thing_name}{$i18n.label.description}
{$template['name']|escape}{$template['description']|escape}{$i18n.label.edit}{$i18n.label.delete}
+
{$forms.templatesForm.btn_add.control}
+ +{if $inactive_templates} +
{$i18n.form.templates.inactive_templates}
+ + + + + + + + {foreach $inactive_templates as $template} + + + + + + + {/foreach} +
{$i18n.label.thing_name}{$i18n.label.description}
{$template['name']|escape}{$template['description']|escape}{$i18n.label.edit}{$i18n.label.delete}
+
{$forms.templatesForm.btn_add.control}
+{/if} + +{if $show_bind_with_projects_checkbox} + + + + + + +{/if} + + + + + + +
{$forms.templatesForm.bind_templates_with_projects.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.templatesForm.prepopulate_note.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
+
{$forms.templatesForm.btn_save.control}
+{$forms.templatesForm.close} diff --git a/WEB-INF/templates/time.tpl b/WEB-INF/templates/time.tpl index 8328ab41d..fa24e8062 100644 --- a/WEB-INF/templates/time.tpl +++ b/WEB-INF/templates/time.tpl @@ -1,163 +1,246 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {include file="time_script.tpl"} - + + +{if $show_navigation} +
+ {$i18n.label.day_view} + {if $user->isPluginEnabled('pu')} / {$i18n.label.puncher}{/if} + {if $user->isPluginEnabled('wv')} / {$i18n.label.week_view}{/if} +
+{/if} {$forms.timeRecordForm.open} - - - - +{if $show_files} + + + + -
- -{if $on_behalf_control} - - - - +
{$forms.timeRecordForm.date.control}
+
{$i18n.label.user}:{$forms.timeRecordForm.onBehalfUser.control}
+ +{if isset($user_dropdown)} + + + + + + {/if} -{if $user->isPluginEnabled('cl')} - - - - +{if $show_client} + + + + + + {/if} -{if $user->isPluginEnabled('iv')} - - - - +{if $show_billable} + + + + + {/if} -{if ($custom_fields && $custom_fields->fields[0])} - - - +{if isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="control_name" value='time_field_'|cat:$timeField['id']} + + + + + + + {/foreach} {/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - +{if $show_project} + + + + + + {/if} -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - +{if $show_task} + + + + + + {/if} -{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - - - - - - - +{if $show_start} + + + + + + + + + + + + {/if} -{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - - - +{if $show_duration} + + + + + + {/if} -
{$forms.timeRecordForm.date.control}
{$forms.timeRecordForm.user.control}
{$i18n.label.client}{if $user->isPluginEnabled('cm')} (*){/if}:{$forms.timeRecordForm.client.control}
{$forms.timeRecordForm.client.control}
 
 
{$custom_fields->fields[0]['label']|escape}{if $custom_fields->fields[0]['required']} (*){/if}:{$forms.timeRecordForm.cf_1.control}
{$forms.timeRecordForm.$control_name.control}
{$i18n.label.project} (*):{$forms.timeRecordForm.project.control}
{$forms.timeRecordForm.project.control}
{$i18n.label.task}:{$forms.timeRecordForm.task.control}
{$forms.timeRecordForm.task.control}
{$i18n.label.start}:{$forms.timeRecordForm.start.control} 
{$i18n.label.finish}:{$forms.timeRecordForm.finish.control} 
{$forms.timeRecordForm.start.control} {$i18n.button.now}
{$forms.timeRecordForm.finish.control}{$i18n.button.now}
{$i18n.label.duration}:{$forms.timeRecordForm.duration.control} {$i18n.form.time.duration_format}
{$forms.timeRecordForm.duration.control}
-
- - -
{$forms.timeRecordForm.date.control}
-
{$forms.timeRecordForm.newfile.control}
- - + +{/if} +{if (isset($template_dropdown) && $template_dropdown)} + - - + + + +{/if} + - + + + +
{$i18n.label.note}:{$forms.timeRecordForm.note.control}{$forms.timeRecordForm.template.control}
{$forms.timeRecordForm.btn_submit.control}{$forms.timeRecordForm.note.control}
{$forms.timeRecordForm.btn_submit.control}
- - - -
{if $time_records} - - - {if $user->isPluginEnabled('cl')} - + + + +
+
{$i18n.label.client}
+ + {if $show_client} + {/if} - {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + {if $show_record_custom_fields && isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + + {/foreach} {/if} - {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + {if $show_project} + {/if} - {if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - + {if $show_task} + {/if} - - - - + {if $show_start} + + + {/if} + + {if $show_note_column} + + {/if} + {if $show_files} + + {/if} + + + {foreach $time_records as $record} - - {if $user->isPluginEnabled('cl')} - + + {if $show_client} + {/if} - {if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + {* record custom fields *} + {if $show_record_custom_fields && isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="control_name" value='time_field_'|cat:$timeField['id']} + + {/foreach} {/if} - {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + {if $show_project} + {/if} - {if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - + {if $show_task} + + {/if} + {if $show_start} + + + {/if} + + {if $show_note_column} + + {/if} + {if $show_files} + {if $record.has_files} + + {else} + + {/if} {/if} - - - + + + {if $show_note_row && $record.comment} + + + + {/if} - - {/foreach} -
{$i18n.label.client}{$i18n.label.project}{$timeField['label']|escape}{$i18n.label.task}{$i18n.label.project}{$i18n.label.start}{$i18n.label.finish}{$i18n.label.task}{$i18n.label.duration}{$i18n.label.note}{$i18n.label.edit}
{$i18n.label.start}{$i18n.label.finish}{$i18n.label.duration}{$i18n.label.note}
{$record.client|escape}{$record.client|escape}{$record.project|escape}{$record.$control_name|escape}{$record.task|escape}{$record.project|escape}{if $record.start}{$record.start}{else} {/if}{if $record.finish}{$record.finish}{else} {/if}{$record.task|escape}{if $record.start}{$record.start}{else} {/if}{if $record.finish}{$record.finish}{else} {/if}{if ($record.duration == '0:00' && $record.start <> '')}{$i18n.form.time.uncompleted}{else}{$record.duration}{/if}{if $record.comment}{$record.comment|escape}{else} {/if}{$i18n.label.files}{$i18n.label.files}{if ($record.duration == '0:00' && $record.start <> '')}{$i18n.form.time.uncompleted}{else}{$record.duration}{/if}{if $record.comment}{$record.comment|escape}{else} {/if} - {if $record.invoice_id} -   + + {if $record.approved || $record.timesheet_id || $record.invoice_id} +   {else} - {$i18n.label.edit} {if ($record.duration == '0:00' && $record.start <> '')} - - - - + {/if} + {$i18n.label.edit} + {/if} + + {if $record.approved || $record.timesheet_id || $record.invoice_id} +   + {else} + {$i18n.label.delete} + {/if} +
{$i18n.label.note}:{$record.comment|escape}
-{/if} -
-{if $time_records} - + +{/if} +{$forms.timeRecordForm.close} + +
+
+ + + + +{if $user->isPluginEnabled('mq')} - - + + {if $over_balance} + + {else} + + {/if} - {if $user->isPluginEnabled('mq')} - + {if $over_quota} - + {else} - + {/if} - {/if} -
{$i18n.label.week_total}: {$week_total}{$i18n.label.day_total}: {$day_total}
{$i18n.label.week_total}: {$week_total}{$i18n.label.day_total}: {$day_total}{$i18n.label.month_total}: {$month_total}{$i18n.form.time.over_balance}: {$balance_remaining}{$i18n.form.time.remaining_balance}: {$balance_remaining}
{$i18n.label.month_total}: {$month_total}{$i18n.label.quota}: {$month_quota}{$i18n.form.time.over_quota}: {$quota_remaining}{$i18n.form.time.over_quota}: {$quota_remaining}{$i18n.form.time.remaining_quota}: {$quota_remaining}{$i18n.form.time.remaining_quota}: {$quota_remaining}
{/if} -{$forms.timeRecordForm.close} + +
diff --git a/WEB-INF/templates/time_delete.tpl b/WEB-INF/templates/time_delete.tpl index b3d8a3972..59d9039d1 100644 --- a/WEB-INF/templates/time_delete.tpl +++ b/WEB-INF/templates/time_delete.tpl @@ -1,50 +1,40 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.timeRecordForm.open} - - - -
- +
-{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - +{if $show_project} + {/if} -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - +{if $show_task} + {/if} -{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - +{if $show_start} + + {/if} -{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - +{if $show_duration} + {/if} - + - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + +{if $show_project} + {/if} -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - +{if $show_task} + {/if} -{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - +{if $show_start} + + {/if} -{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - +{if $show_duration} + {/if} - - -
{$i18n.label.project}{$i18n.label.project}{$i18n.label.task}{$i18n.label.task}{$i18n.label.start}{$i18n.label.finish}{$i18n.label.start}{$i18n.label.finish}{$i18n.label.duration}{$i18n.label.duration}{$i18n.label.note}{$i18n.label.note}
{$time_rec.project_name|escape}
{$time_rec.project_name|escape}{$time_rec.task_name|escape}{$time_rec.task_name|escape}{if $time_rec.start}{$time_rec.start}{else} {/if}{if $time_rec.finish<>$time_rec.start}{$time_rec.finish}{else} {/if}{if $time_rec.start}{$time_rec.start}{else} {/if}{if $time_rec.finish<>$time_rec.start}{$time_rec.finish}{else} {/if}{if ($time_rec.duration == '0:00' && $time_rec.start <> '')}{$i18n.form.time.uncompleted}{else}{$time_rec.duration}{/if}{if ($time_rec.duration == '0:00' && $time_rec.start <> '')}{$i18n.form.time.uncompleted}{else}{$time_rec.duration}{/if}{if $time_rec.comment}{$time_rec.comment|escape}{else} {/if}
- - - - - - + -
 
{$forms.timeRecordForm.delete_button.control}  {$forms.timeRecordForm.cancel_button.control}{if $time_rec.comment}{$time_rec.comment|escape}{else} {/if}
-
+
{$forms.timeRecordForm.delete_button.control} {$forms.timeRecordForm.cancel_button.control}
{$forms.timeRecordForm.close} diff --git a/WEB-INF/templates/time_edit.tpl b/WEB-INF/templates/time_edit.tpl index f69321570..8425965b9 100644 --- a/WEB-INF/templates/time_edit.tpl +++ b/WEB-INF/templates/time_edit.tpl @@ -1,78 +1,116 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {include file="time_script.tpl"} +{* Conditional include of confirmSave handler. *} +{if isset($confirm_save) && $confirm_save} + +{/if} + {$forms.timeRecordForm.open} - - - + + + + +
- +
- - -
- -{if $user->isPluginEnabled('cl')} - - - - +{if $show_client} + + + + + + {/if} -{if $user->isPluginEnabled('iv')} - - - - +{if $show_billable} + + + + + {/if} -{if ($custom_fields && $custom_fields->fields[0])} - - - +{if $show_paid_status} + + + + + {/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - +{if isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="control_name" value='time_field_'|cat:$timeField['id']} + + + + + + + {/foreach} {/if} -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - +{if $show_project} + + + + + + {/if} -{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - - - - - - - +{if $show_task} + + + + + + +{/if} +{if $show_start} + + + + + + + + + + + + +{/if} +{if $show_duration} + + + + + + {/if} -{if (($smarty.const.TYPE_DURATION == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - - - + + + + + + +{if (isset($template_dropdown) && $template_dropdown)} + + + + + + {/if} - - - - - - - - - - - - - - - -
{$i18n.label.client}{if $user->isPluginEnabled('cm')} (*){/if}:{$forms.timeRecordForm.client.control}
{$forms.timeRecordForm.client.control}
 
 
{$custom_fields->fields[0]['label']|escape}{if $custom_fields->fields[0]['required']} (*){/if}:{$forms.timeRecordForm.cf_1.control}
 
{$i18n.label.project} (*):{$forms.timeRecordForm.project.control}
{$forms.timeRecordForm.$control_name.control}
{$i18n.label.task}:{$forms.timeRecordForm.task.control}
{$forms.timeRecordForm.project.control}
{$i18n.label.start}:{$forms.timeRecordForm.start.control} 
{$i18n.label.finish}:{$forms.timeRecordForm.finish.control} 
{$forms.timeRecordForm.task.control}
{$forms.timeRecordForm.start.control} {$i18n.button.now}
{$forms.timeRecordForm.finish.control}{$i18n.button.now}
{$forms.timeRecordForm.duration.control}
{$i18n.label.duration}:{$forms.timeRecordForm.duration.control} {$i18n.form.time.duration_format}
{$forms.timeRecordForm.date.control}
{$forms.timeRecordForm.template.control}
{$i18n.label.date}:{$forms.timeRecordForm.date.control}
{$i18n.label.note}:{$forms.timeRecordForm.note.control}
 
{$forms.timeRecordForm.btn_save.control} {$forms.timeRecordForm.btn_copy.control} {$forms.timeRecordForm.btn_delete.control}
-
-
{$forms.timeRecordForm.note.control}
+
{$forms.timeRecordForm.btn_save.control} {$forms.timeRecordForm.btn_copy.control} {$forms.timeRecordForm.btn_delete.control}
{$forms.timeRecordForm.close} diff --git a/WEB-INF/templates/time_script.tpl b/WEB-INF/templates/time_script.tpl index 275f74c6b..0dafae8cd 100644 --- a/WEB-INF/templates/time_script.tpl +++ b/WEB-INF/templates/time_script.tpl @@ -12,7 +12,7 @@ // project_ids[143] = "325,370,390,400"; // Comma-separated list of project ids for client. // project_names[325] = "Time Tracker"; // Project name. // task_ids[325] = "100,101,302,303,304"; // Comma-separated list ot task ids for project. -// task_names[100] = "Coding"; // Task name. +// template_ids[325] = "17,21"; // Comma-separated list ot template ids for project. NOT YET IMPLEMENTED. // Prepare an array of project ids for clients. var project_ids = new Array(); @@ -37,23 +37,52 @@ var task_ids = new Array(); {foreach $project_list as $project} task_ids[{$project.id}] = "{$project.tasks}"; {/foreach} -// Prepare an array of task names. -var task_names = new Array(); -{foreach $task_list as $task} - task_names[{$task.id}] = "{$task.name|escape:'javascript'}"; -{/foreach} + +// Prepare an array of template ids for projects. +var template_ids = new Array(); +{if (isset($bind_templates_with_projects) && $bind_templates_with_projects)} + {foreach $project_list as $project} + template_ids[{$project.id}] = "{$project.templates}"; + {/foreach} +{/if} +// Prepare an array of template names. +var template_names = new Array(); +{if (isset($bind_templates_with_projects) && $bind_templates_with_projects) && isset($template_list)} + {foreach $template_list as $template} + template_names[{$template.id}] = "{$template.name|escape:'javascript'}"; + {/foreach} +{/if} +// Prepare an array of template bodies. +var template_bodies = new Array(); +{if isset($template_list)} + {foreach $template_list as $template} + template_bodies[{$template.id}] = "{$template.content|escape:'javascript'}"; + {/foreach} +{/if} + +// The fillNote function populates the Note field with a selected template body. +function fillNote(id) { + if (!id) return; // Do nothing. + var template_body = template_bodies[id]; + var note = document.getElementById("note"); + note.value = template_body; +} // Mandatory top options for project and task dropdowns. var empty_label_project = "{$i18n.dropdown.select|escape:'javascript'}"; var empty_label_task = "{$i18n.dropdown.select|escape:'javascript'}"; +var empty_label_template = "{$i18n.dropdown.select|escape:'javascript'}"; -// The fillDropdowns function populates the "project" and "task" dropdown controls +// The fillDropdowns function populates the "project", "task", and "template" dropdown controls // with relevant values. function fillDropdowns() { if(document.body.contains(document.timeRecordForm.client)) fillProjectDropdown(document.timeRecordForm.client.value); - fillTaskDropdown(document.timeRecordForm.project.value); + if(document.body.contains(document.timeRecordForm.project)) { + fillTaskDropdown(document.timeRecordForm.project.value); + fillTemplateDropdown(document.timeRecordForm.project.value); + } } // The fillProjectDropdown function populates the project combo box with @@ -108,7 +137,6 @@ function fillProjectDropdown(id) { // tasks associated with a selected project (project id is passed here as id). function fillTaskDropdown(id) { var str_ids = task_ids[id]; - var dropdown = document.getElementById("task"); if (dropdown == null) return; // Nothing to do. @@ -120,7 +148,60 @@ function fillTaskDropdown(id) { // Add mandatory top option. dropdown.options[0] = new Option(empty_label_task, '', true); - // Populate the dropdown from the task_names array. + // Populate the Task dropdown. + if (str_ids) { + var ids = new Array(); + ids = str_ids.split(","); + var len = ids.length; + + // Iterate through $task_list because it is sorted by upper(name). + var idx = 1; + {foreach $task_list as $task} + if (ids.includes("{$task.id}")) { + dropdown.options[idx] = new Option("{$task.name|escape:'javascript'}", {$task.id}); + idx++; + } + {/foreach} + + // If a previously selected item is still in dropdown - select it. + if (dropdown.options.length > 0) { + for (var i = 0; i < dropdown.options.length; i++) { + if (dropdown.options[i].value == selected_item) { + dropdown.options[i].selected = true; + } + } + } + +{if $user->getConfigOption('task_required')} + // Select a task if user is required to do so and there is only one task available. + if (dropdown.options.length == 2) { // 2 because of mandatory top option. + dropdown.options[1].selected = true; + } +{/if} + } +} + +// The fillTemplateDropdown function populates the template combo box with +// templates associated with a selected project (project id is passed here as id). +function fillTemplateDropdown(id) { +{if (!isset($bind_templates_with_projects) || !$bind_templates_with_projects)} + return; // Do nothing if we are not binding templates with projects, +{/if} + + var str_ids = template_ids[id]; + + var dropdown = document.getElementById("template"); + if (dropdown == null) return; // Nothing to do. + + // Determine previously selected item. + var selected_item = dropdown.options[dropdown.selectedIndex].value; + + // Remove existing content. + dropdown.length = 0; + // Add mandatory top option. + dropdown.options[0] = new Option(empty_label_template, '', true); + + // Populate the dropdown from the template_names array. if (str_ids) { var ids = new Array(); ids = str_ids.split(","); @@ -129,8 +210,8 @@ function fillTaskDropdown(id) { var idx = 1; for (var i = 0; i < len; i++) { var t_id = ids[i]; - if (task_names[t_id]) { - dropdown.options[idx] = new Option(task_names[t_id], t_id); + if (template_names[t_id]) { + dropdown.options[idx] = new Option(template_names[t_id], t_id); idx++; } } @@ -146,6 +227,21 @@ function fillTaskDropdown(id) { } } +// The prepopulateNote function populates the note field with first found template body in Template dropdown. +function prepopulateNote() { + {if (!isset($prepopulate_note) || !$prepopulate_note)} + return; + {/if} + var dropdown = document.getElementById("template"); + if (dropdown == null) return; // Nothing to do. + + if (dropdown.options.length <= 1) return ; // 1 because of mandatory top option. + + dropdown.options[1].selected = true; // Select first available template. + var note = document.getElementById("note"); + note.value = template_bodies[dropdown.options[1].value]; // Prepolulate the Note field with first template body. +} + // The formDisable function disables some fields depending on what we have in other fields. function formDisable(formField) { var formFieldValue = eval("document.timeRecordForm." + formField + ".value"); @@ -153,20 +249,34 @@ function formDisable(formField) { var x; if (((formFieldValue != "") && (formFieldName == "start")) || ((formFieldValue != "") && (formFieldName == "finish"))) { + // Either start or finish field not empty. x = eval("document.timeRecordForm.duration"); x.value = ""; x.disabled = true; x.style.background = "#e9e9e9"; + return; } if (((formFieldValue == "") && (formFieldName == "start") && (document.timeRecordForm.finish.value == "")) || ((formFieldValue == "") && (formFieldName == "finish") && (document.timeRecordForm.start.value == ""))) { + // Both start and finish fields are emtpy. x = eval("document.timeRecordForm.duration"); x.value = ""; x.disabled = false; x.style.background = "white"; + return; + } + + if (((formFieldValue == "") && (formFieldName == "start")) || ((formFieldValue == "") && (formFieldName == "finish"))) { + // Either start or finish field is empty. + x = eval("document.timeRecordForm.duration"); + x.value = ""; + x.disabled = true; + x.style.background = "#e9e9e9"; + return; } if ((formFieldValue != "") && (formFieldName == "duration")) { + // Duration field is not empty. x = eval("document.timeRecordForm.start"); x.value = ""; x.disabled = true; @@ -175,9 +285,11 @@ function formDisable(formField) { x.value = ""; x.disabled = true; x.style.background = "#e9e9e9"; + return; } if ((formFieldValue == "") && (formFieldName == "duration")) { + // Duration field is empty. x = eval("document.timeRecordForm.start"); x.disabled = false; x.style.background = "white"; @@ -196,7 +308,7 @@ function setNow(formField) { x.disabled = false; x.style.background = "white"; var today = new Date(); - var time_format = '{$user->time_format}'; + var time_format = '{$user->getTimeFormat()}'; var obj = eval("document.timeRecordForm." + formField); obj.value = today.strftime(time_format); formDisable(formField); @@ -211,4 +323,22 @@ function get_time() { var date = new Date(); return date.strftime("%H:%M"); } + +// adjustTodayLinks adjusts today links to match today in user browser on load and also on click. +function adjustTodayLinks() { + var today_links = document.getElementsByClassName("today_link"); + var i; + var browser_today = new Date(); + for (i = 0; i < today_links.length; i++) { + today_links[i].href = '?date='+browser_today.strftime("%Y-%m-%d"); + today_links[i].onclick = function() { + var today = new Date(); + var links = document.getElementsByClassName("today_link"); + var j; + for (j = 0; j < links.length; j++) { + links[j].href = '?date='+today.strftime("%Y-%m-%d"); + } + } + } +} diff --git a/WEB-INF/templates/timesheet_add.tpl b/WEB-INF/templates/timesheet_add.tpl new file mode 100644 index 000000000..f69a8ff74 --- /dev/null +++ b/WEB-INF/templates/timesheet_add.tpl @@ -0,0 +1,48 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.timesheetForm.open} + + + + + + + +{if $show_client} + + + + + + +{/if} + + + + + + + + + + + + + + + + + + + + + + + + + + +
{$forms.timesheetForm.timesheet_name.control}
{$forms.timesheetForm.client.control}
{$forms.timesheetForm.project.control}
{$forms.timesheetForm.start.control}
{$forms.timesheetForm.finish.control}
{$forms.timesheetForm.comment.control}
{$i18n.label.required_fields}
+
{$forms.timesheetForm.btn_add.control}
+{$forms.timesheetForm.close} diff --git a/WEB-INF/templates/timesheet_delete.tpl b/WEB-INF/templates/timesheet_delete.tpl new file mode 100644 index 000000000..a3e7b7312 --- /dev/null +++ b/WEB-INF/templates/timesheet_delete.tpl @@ -0,0 +1,7 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.timesheetDeleteForm.open} +
{$timesheet_to_delete|escape}
+
{$forms.timesheetDeleteForm.btn_delete.control} {$forms.timesheetDeleteForm.btn_cancel.control}
+{$forms.timesheetDeleteForm.close} diff --git a/WEB-INF/templates/timesheet_edit.tpl b/WEB-INF/templates/timesheet_edit.tpl new file mode 100644 index 000000000..62cf01a8c --- /dev/null +++ b/WEB-INF/templates/timesheet_edit.tpl @@ -0,0 +1,28 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.timesheetForm.open} + + + + + + + + + + + + + + + + + + + + + +
{$forms.timesheetForm.timesheet_name.control}
{$forms.timesheetForm.comment.control}
{$forms.timesheetForm.status.control}
{$i18n.label.required_fields}
+
{$forms.timesheetForm.btn_save.control} {if $can_delete}{$forms.timesheetForm.btn_delete.control}{/if}
+{$forms.timesheetForm.close} diff --git a/WEB-INF/templates/timesheet_view.tpl b/WEB-INF/templates/timesheet_view.tpl new file mode 100644 index 000000000..a7d8b0e14 --- /dev/null +++ b/WEB-INF/templates/timesheet_view.tpl @@ -0,0 +1,82 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + + + +
{$timesheet['name']|escape}
+ +{if $user->behalfUser} + + + + +{/if} +{if $timesheet['client_id']} + + + + +{/if} +{if $timesheet['project_id']} + + + + +{/if} +{if $timesheet['comment']} + + + + +{/if} +{if $timesheet['approve_status'] == null} + + + + +{/if} +{if $timesheet['approve_status'] != null} + + + + +{/if} +{if $timesheet['approve_comment']} + + + + +{/if} +
{$i18n.label.user}:{$timesheet['user_name']|escape}
{$i18n.label.client}:{$timesheet['client_name']|escape}
{$i18n.label.project}:{$timesheet['project_name']|escape}
{$i18n.label.comment}:{$timesheet['comment']|escape}
{$i18n.label.submitted}:{if $timesheet.submit_status}{$i18n.label.yes}{else}{$i18n.label.no}{/if}
{$i18n.label.approved}:{if $timesheet.approve_status}{$i18n.label.yes}{else}{$i18n.label.no}{/if}
{$i18n.label.note}:{$timesheet['approve_comment']|escape}
+
+ + + + + + {foreach $subtotals as $subtotal} + + + + + {/foreach} + + + + +
{$group_by_header|escape}{$i18n.label.duration}
{if $subtotal['name']}{$subtotal['name']|escape}{else} {/if}{$subtotal['time']}
{$i18n.label.total}:{$totals['time']}
+ +{$forms.timesheetForm.open} +{if $show_submit} +
{if $show_approvers}{$i18n.form.mail.to}: {$forms.timesheetForm.approver.control}{/if} {$forms.timesheetForm.btn_submit.control}
+{/if} +{if $show_approve} + + + +
{$forms.timesheetForm.comment.control}
+
{$forms.timesheetForm.btn_approve.control} {$forms.timesheetForm.btn_disapprove.control}
+{/if} +{$forms.timesheetForm.close} diff --git a/WEB-INF/templates/timesheets.tpl b/WEB-INF/templates/timesheets.tpl new file mode 100644 index 000000000..2bd927369 --- /dev/null +++ b/WEB-INF/templates/timesheets.tpl @@ -0,0 +1,104 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + + + +{$forms.timesheetsForm.open} + +{if isset($user_dropdown)} + + + + + + +{/if} +
{$forms.timesheetsForm.user.control}
+{$forms.timesheetsForm.close} +
+{if $inactive_timesheets} +
{$i18n.form.timesheets.active_timesheets}
+{/if} + + + + {if $show_client} + + {/if} + + + {if $show_files} + + {/if} + + + + {foreach $active_timesheets as $timesheet} + + + {if $show_client} + + {/if} + + {if $timesheet.approve_status == null} + + {else} + + {/if} + {if $show_files} + {if $timesheet.has_files} + + {else} + + {/if} + {/if} + + + + {/foreach} +
{$i18n.label.thing_name}{$i18n.label.client}{$i18n.label.submitted}{$i18n.label.approved}
{$timesheet.name|escape}{$timesheet.client_name|escape}{if $timesheet.submit_status}{$i18n.label.yes}{else}{$i18n.label.no}{/if}{if $timesheet.approve_status}{$i18n.label.yes}{else}{$i18n.label.no}{/if}{$i18n.label.files}{$i18n.label.files}{$i18n.label.edit}{$i18n.label.delete}
+
+{if $inactive_timesheets} +
{$i18n.form.timesheets.inactive_timesheets}
+ + + + {if $show_client} + + {/if} + + + {if $show_files} + + {/if} + + + + {foreach $inactive_timesheets as $timesheet} + + + {if $show_client} + + {/if} + + {if $timesheet.approve_status == null} + + {else} + + {/if} + {if $show_files} + {if $timesheet.has_files} + + {else} + + {/if} + {/if} + + + + {/foreach} +
{$i18n.label.thing_name}{$i18n.label.client}{$i18n.label.submitted}{$i18n.label.approved}
{$timesheet.name|escape}{$timesheet.client_name|escape}{if $timesheet.submit_status}{$i18n.label.yes}{else}{$i18n.label.no}{/if}{if $timesheet.approve_status}{$i18n.label.yes}{else}{$i18n.label.no}{/if}{$i18n.label.files}{$i18n.label.files}{$i18n.label.edit}{$i18n.label.delete}
+
+{/if} diff --git a/WEB-INF/templates/user_add.tpl b/WEB-INF/templates/user_add.tpl index 297140444..7275b645e 100644 --- a/WEB-INF/templates/user_add.tpl +++ b/WEB-INF/templates/user_add.tpl @@ -1,4 +1,16 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.userForm.open} - -
- - - - - - - - +
{$i18n.label.person_name} (*):{$forms.userForm.name.control}
{$i18n.label.login} (*):{$forms.userForm.login.control}
+ + + + + + + + + + + + {if !$auth_external} - - - - - - - - + + + + + + + + + + + + +{/if} + + + + + + + + + + + +{if $user->isPluginEnabled('cl')} + + + + + + + + +{/if} + +{if $show_quota} + + + + + + {/if} - - - - -{if $user->isManager()} - - - - +{if isset($custom_fields) && $custom_fields->userFields} + {foreach $custom_fields->userFields as $userField} + {assign var="control_name" value='user_field_'|cat:$userField['id']} + + + + + + + {/foreach} {/if} - - - - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - - - - + + + + + + + +{if $show_projects} + + + + + + + + + {/if} - - - -
{$forms.userForm.name.control}
{$forms.userForm.login.control}
{$i18n.label.password} (*):{$forms.userForm.pas1.control}
{$i18n.label.confirm_password} (*):{$forms.userForm.pas2.control}
{$forms.userForm.pas1.control}
{$forms.userForm.pas2.control}
{$forms.userForm.email.control}
{$forms.userForm.role.control}
{$forms.userForm.client.control}
{$forms.userForm.quota_percent.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$i18n.label.email}:{$forms.userForm.email.control}
{$i18n.form.users.role}:{$forms.userForm.role.control} {$forms.userForm.client.control}
{$forms.userForm.$control_name.control}
{$i18n.form.users.default_rate} (0{$user->decimal_mark}00):{$forms.userForm.rate.control}
{$i18n.label.projects}:{$forms.userForm.projects.control}
{$i18n.label.required_fields}
{$forms.userForm.rate.control}
{$forms.userForm.projects.control}
{$forms.userForm.btn_submit.control}
+ {$i18n.label.required_fields} +
+ + {$forms.userForm.btn_submit.control} + +
{$forms.userForm.close} diff --git a/WEB-INF/templates/user_delete.tpl b/WEB-INF/templates/user_delete.tpl index e85d97f1f..63e9f795f 100644 --- a/WEB-INF/templates/user_delete.tpl +++ b/WEB-INF/templates/user_delete.tpl @@ -1,20 +1,7 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.userDeleteForm.open} - - - - -
- - - - - - - - - - - -
{$user_to_delete|escape}
 
{$forms.userDeleteForm.btn_delete.control}  {$forms.userDeleteForm.btn_cancel.control}
-
+
{$user_to_delete|escape}
+
{$forms.userDeleteForm.btn_delete.control} {$forms.userDeleteForm.btn_cancel.control}
{$forms.userDeleteForm.close} diff --git a/WEB-INF/templates/user_edit.tpl b/WEB-INF/templates/user_edit.tpl index 7f81ca032..e4d916a5c 100644 --- a/WEB-INF/templates/user_edit.tpl +++ b/WEB-INF/templates/user_edit.tpl @@ -1,4 +1,16 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + {$forms.userForm.open} - -
- - - - - - - - +
{$i18n.label.person_name} (*):{$forms.userForm.name.control}
{$i18n.label.login} (*):{$forms.userForm.login.control}
+ + + + + + + + + + + + {if !$auth_external} - - - - - - - - + + + + + + + + + + + + +{/if} + + + + + +{if $user->id != $user_id} + + + + + + +{if $user->isPluginEnabled('cl')} + + + + + + + + +{/if} + + + + + + +{/if} +{if $user->id == $user_id} + + + + + + {/if} - - - - -{if $user->isManager() && ($user->id != $user_id)} - - - - + +{if $show_quota} + + + + + + {/if} -{* Prohibit deactivating team manager. Deactivating others is ok. *} -{if $user->canManageTeam() && !($user->isManager() && $user->id == $user_id)} - - - - +{if isset($custom_fields) && $custom_fields->userFields} + {foreach $custom_fields->userFields as $userField} + {assign var="control_name" value='user_field_'|cat:$userField['id']} + + + + + + + {/foreach} {/if} - - - - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - + + + + + + + +{if $show_projects} + + + + + + + + + {/if} - - - - - - -
{$forms.userForm.name.control}
{$forms.userForm.login.control}
{$i18n.label.password} (*):{$forms.userForm.pas1.control}
{$i18n.label.confirm_password} (*):{$forms.userForm.pas2.control}
{$forms.userForm.pas1.control}
{$forms.userForm.pas2.control}
{$forms.userForm.email.control}
{$forms.userForm.role.control}
{$forms.userForm.client.control}
{$forms.userForm.status.control}
{$user->role_name} {if $can_swap}{$i18n.form.user_edit.swap_roles}{/if}
{$i18n.label.email}:{$forms.userForm.email.control}
{$i18n.form.users.role}:{$forms.userForm.role.control} {$forms.userForm.client.control}
{$forms.userForm.quota_percent.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$i18n.label.status}:{$forms.userForm.status.control}
{$forms.userForm.$control_name.control}
{$i18n.form.users.default_rate} (0{$user->decimal_mark}00):{$forms.userForm.rate.control}
{$i18n.label.projects}:{$forms.userForm.projects.control}
{$forms.userForm.rate.control}
{$forms.userForm.projects.control}
{$i18n.label.required_fields}
{$forms.userForm.btn_submit.control}
+ {$i18n.label.required_fields} +
+ + {$forms.userForm.btn_submit.control} + +
{$forms.userForm.close} diff --git a/WEB-INF/templates/users.tpl b/WEB-INF/templates/users.tpl index 3ea9ceb3d..2a226238d 100644 --- a/WEB-INF/templates/users.tpl +++ b/WEB-INF/templates/users.tpl @@ -1,132 +1,106 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + - - -
-{if $user->canManageTeam()} - - {if $inactive_users} - - {/if} - - - - - - - +{if $user->can('manage_users')} + {if $inactive_users}
{$i18n.label.active_users}
{/if} {if $active_users} +
{$i18n.form.users.active_users}
{$i18n.label.person_name}{$i18n.label.login}{$i18n.form.users.role}{$i18n.label.edit}{$i18n.label.delete}
+ + + + + {if $show_quota} + + {/if} + + + {foreach $active_users as $u} - - - - {if $smarty.const.ROLE_MANAGER == $u.role} - - {elseif $smarty.const.ROLE_COMANAGER == $u.role} - - {elseif $smarty.const.ROLE_CLIENT == $u.role} - - {elseif $smarty.const.ROLE_USER == $u.role} - + + - + {$u.name|escape} + + + + {if $show_quota} + + {/if} + {if $u.group_id != $user->group_id || $u.rank < $user->rank || ($u.rank == $user->rank && $u.id == $user->id)} + + {if $u.id != $user->id}{else}{/if} {else} - - - + + {/if} - + {/foreach} {/if} -
{$i18n.label.person_name}{$i18n.label.login}{$i18n.form.users.role}{$i18n.label.quota}
- {if $smarty.const.UNCOMPLETED_INDICATORS == $user->uncompleted_indicators} - - {/if} - {$u.name|escape} - {$u.login|escape}{$i18n.form.users.manager}{$i18n.form.users.comanager}{$i18n.label.client}{$i18n.label.user}
+ {if isset($uncompleted_indicators) && $uncompleted_indicators} + {if $u.has_uncompleted_entry_today} + + {elseif $u.has_uncompleted_entry} + + {else} + + {/if} {/if} - {if $user->isManager()} - - {$i18n.label.edit}{if $smarty.const.ROLE_MANAGER != $u.role || $can_delete_manager}{$i18n.label.delete}{/if}{$u.login|escape}{$u.role_name|escape}{$u.quota_percent}{$i18n.label.edit}{$i18n.label.delete}{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}{$i18n.label.edit}{/if}{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}{$i18n.label.delete}{/if}
- - - - - -

-
-
- +
+
{if $inactive_users} - - - - - - - - - +
{$i18n.label.inactive_users}
+
{$i18n.form.users.inactive_users}
{$i18n.label.person_name}{$i18n.label.login}{$i18n.form.users.role}{$i18n.label.edit}{$i18n.label.delete}
+ + + + + {if $show_quota} + + {/if} + + + {foreach $inactive_users as $u} - - - - {if $smarty.const.ROLE_MANAGER == $u.role} - - {elseif $smarty.const.ROLE_COMANAGER == $u.role} - - {elseif $smarty.const.ROLE_CLIENT == $u.role} - - {elseif $smarty.const.ROLE_USER == $u.role} - + + + + + {if $show_quota} + {/if} - {if $user->isManager()} - - - + {if $u.group_id != $user->group_id || $u.rank < $user->rank} + + {else} - - - + + {/if} - + {/foreach} - -
{$i18n.label.person_name}{$i18n.label.login}{$i18n.form.users.role}{$i18n.label.quota}
{$u.name|escape}{$u.login|escape}{$i18n.form.users.manager}{$i18n.form.users.comanager}{$i18n.label.client}{$i18n.label.user}
{$u.name|escape}{$u.login|escape}{$u.role_name|escape}{$u.quota_percent}{$i18n.label.edit}{if $smarty.const.ROLE_MANAGER != $u.role || $can_delete_manager}{$i18n.label.delete}{/if}{$i18n.label.edit}{$i18n.label.delete}{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}{$i18n.label.edit}{/if}{if ($user->id == $u.id) || ($smarty.const.ROLE_CLIENT == $u.role) || ($smarty.const.ROLE_USER == $u.role)}{$i18n.label.delete}{/if}
- - - - - -
-
-
+ +
{/if} {else} - - - - - - +
{$i18n.label.person_name}{$i18n.label.login}{$i18n.form.users.role}
+ + + + + {foreach $active_users as $u} - - - - {if $smarty.const.ROLE_MANAGER == $u.role} - - {elseif $smarty.const.ROLE_COMANAGER == $u.role} - - {elseif $smarty.const.ROLE_CLIENT == $u.role} - - {elseif $smarty.const.ROLE_USER == $u.role} - + + - {/foreach} -
{$i18n.label.person_name}{$i18n.label.login}{$i18n.form.users.role}
{$u.name|escape}{$u.login|escape}{$i18n.form.users.manager}{$i18n.form.users.comanager}{$i18n.label.client}{$i18n.label.user}
+ {if $uncompleted_indicators} + {/if} -
-{/if} + {$u.name|escape} + {$u.login|escape} + {$u.role_name|escape} + {/foreach} +{/if} diff --git a/WEB-INF/templates/week.tpl b/WEB-INF/templates/week.tpl new file mode 100644 index 000000000..6032d4eb5 --- /dev/null +++ b/WEB-INF/templates/week.tpl @@ -0,0 +1,199 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{include file="time_script.tpl"} + + + +{if $show_navigation} + +{/if} + +{$forms.weekTimeForm.open} +
{$forms.weekTimeForm.date.control}
+ + +{if isset($user_dropdown)} + + + + + + +{/if} +{if $show_client} + + + + + + +{/if} +{if $show_billable} + + + + + +{/if} +{if isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="control_name" value='time_field_'|cat:$timeField['id']} + + + + + + + {/foreach} +{/if} +{if $show_project} + + + + + + +{/if} +{if $show_task} + + + + + + +{/if} +{if $show_week_note} + + + + + + +{/if} +
{$forms.weekTimeForm.date.control}
{$forms.weekTimeForm.user.control}
{$forms.weekTimeForm.client.control}
 
{$forms.weekTimeForm.$control_name.control}
{$forms.weekTimeForm.project.control}
{$forms.weekTimeForm.task.control}
{$forms.weekTimeForm.comment.control}
+
+ + + + +
{$forms.weekTimeForm.week_durations.control}
+
{$forms.weekTimeForm.btn_submit.control}
+{$forms.weekTimeForm.close} + +{if $show_week_list} +
+ + + + {if $show_record_custom_fields && isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + + {/foreach} + {/if} + {if $show_client} + + {/if} + {if $show_project} + + {/if} + {if $show_task} + + {/if} + {if $show_start} + + + {/if} + + + {if $show_files} + + {/if} + + + + {foreach $time_records as $record} + + + {* record custom fields *} + {if $show_record_custom_fields && isset($custom_fields) && $custom_fields->timeFields} + {foreach $custom_fields->timeFields as $timeField} + {assign var="control_name" value='time_field_'|cat:$timeField['id']} + + {/foreach} + {/if} + {if $show_client} + + {/if} + {if $show_project} + + {/if} + {if $show_task} + + {/if} + {if $show_start} + + + {/if} + + + {if $show_files} + {if $record.has_files} + + {else} + + {/if} + {/if} + + + + {/foreach} +
{$i18n.label.date}{$timeField['label']|escape}{$i18n.label.client}{$i18n.label.project}{$i18n.label.task}{$i18n.label.start}{$i18n.label.finish}{$i18n.label.duration}{$i18n.label.note}
{$record.date}{$record.$control_name|escape}{$record.client|escape}{$record.project|escape}{$record.task|escape}{if $record.start}{$record.start}{else} {/if}{if $record.finish}{$record.finish}{else} {/if}{if ($record.duration == '0:00' && $record.start <> '')}{$i18n.form.time.uncompleted}{else}{$record.duration}{/if}{if $record.comment}{$record.comment|escape}{else} {/if}{$i18n.label.files}{$i18n.label.files} + {if (isset($record.approved) && $record.approved) || (isset($record.timesheet_id) && $record.timesheet_id) || (isset($record.invoice_id) && $record.invoice_id)} +   + {else} + {$i18n.label.edit} + {/if} + + {if (isset($record.approved) && $record.approved) || (isset($record.timesheet_id) && $record.timesheet_id) || (isset($record.invoice_id) && $record.invoice_id)} +   + {else} + {$i18n.label.delete} + {/if} +
+{/if} +{if $time_records} +
+ + + + + + {if $user->isPluginEnabled('mq')} + + + {if $over_quota} + + {else} + + {/if} + + {/if} +
{$i18n.label.week_total}: {$week_total}
{$i18n.label.month_total}: {$month_total}{$i18n.form.time.over_quota}: {$quota_remaining}{$i18n.form.time.remaining_quota}: {$quota_remaining}
+
+{/if} diff --git a/WEB-INF/templates/week_view.tpl b/WEB-INF/templates/week_view.tpl new file mode 100644 index 000000000..9cd7b90a2 --- /dev/null +++ b/WEB-INF/templates/week_view.tpl @@ -0,0 +1,53 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.weekViewForm.open} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{$forms.weekViewForm.week_menu.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.weekViewForm.week_note.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.weekViewForm.week_list.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.weekViewForm.notes.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.weekViewForm.weekends.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
+
{$forms.weekViewForm.btn_save.control}
+{$forms.weekViewForm.close} diff --git a/WEB-INF/templates/work_units.tpl b/WEB-INF/templates/work_units.tpl new file mode 100644 index 000000000..a2b5626f5 --- /dev/null +++ b/WEB-INF/templates/work_units.tpl @@ -0,0 +1,35 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.workUnitsForm.open} + + + + + + + + + + + + + + + + + + + +
{$forms.workUnitsForm.minutes_in_unit.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.workUnitsForm.1st_unit_threshold.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
{$forms.workUnitsForm.totals_only.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it} +
+
{$forms.workUnitsForm.btn_save.control}
+{$forms.workUnitsForm.close} diff --git a/access_denied.php b/access_denied.php index d5c58fe22..80900d570 100644 --- a/access_denied.php +++ b/access_denied.php @@ -1,36 +1,12 @@ add($i18n->getKey('error.access_denied')); -if ($auth->isAuthenticated()) $GLOBALS['SMARTY']->assign('authenticated', true); // Used in header.tpl for menu display. +$err->add($i18n->get('error.access_denied')); +if ($auth->isAuthenticated()) $smarty->assign('authenticated', true); // Used in header.tpl for menu display. -$smarty->assign('title', $i18n->getKey('label.error')); +$smarty->assign('title', $i18n->get('label.error')); $smarty->assign('content_page_name', 'access_denied.tpl'); $smarty->display('index.tpl'); diff --git a/admin_group_add.php b/admin_group_add.php new file mode 100644 index 000000000..9aeff1831 --- /dev/null +++ b/admin_group_add.php @@ -0,0 +1,108 @@ +isPost()) { + $cl_group_name = trim($request->getParameter('group_name')); + $cl_lang = $request->getParameter('lang'); + $cl_manager_name = trim($request->getParameter('manager_name')); + $cl_manager_login = trim($request->getParameter('manager_login')); + if (!$auth->isPasswordExternal()) { + $cl_password1 = $request->getParameter('password1'); + $cl_password2 = $request->getParameter('password2'); + } + $cl_manager_email = trim($request->getParameter('manager_email')); +} else + $cl_lang = $i18n->lang; // Browser setting from initialize.php. + +$form = new Form('groupForm'); +$form->addInput(array('type'=>'text','maxlength'=>'200','name'=>'group_name','value'=>$cl_group_name)); + +// Prepare an array of available languages. +$lang_files = I18n::getLangFileList(); +foreach ($lang_files as $lfile) { + $content = file(RESOURCE_DIR."/".$lfile); + $lname = ''; + foreach ($content as $line) { + if (strstr($line, 'i18n_language')) { + $a = explode('=', $line); + $lname = trim(str_replace(';','',str_replace("'","",$a[1]))); + break; + } + } + unset($content); + $longname_lang[] = array('id'=>I18n::getLangFromFilename($lfile),'name'=>$lname); +} +$longname_lang = mu_sort($longname_lang, 'name'); +$form->addInput(array('type'=>'combobox','name'=>'lang','data'=>$longname_lang,'datakeys'=>array('id','name'),'value'=>$cl_lang)); + +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_name','value'=>$cl_manager_name)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_login','value'=>$cl_manager_login)); +if (!$auth->isPasswordExternal()) { + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'password1','value'=>$cl_password1)); + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'password2','value'=>$cl_password2)); +} +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_email','value'=>$cl_manager_email)); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.submit'))); + +if ($request->isPost()) { + // Validate user input. + if (!ttValidString($cl_group_name)) + $err->add($i18n->get('error.field'), $i18n->get('label.group_name')); + if (!ttValidString($cl_manager_name)) + $err->add($i18n->get('error.field'), $i18n->get('label.manager_name')); + if (!ttValidString($cl_manager_login)) + $err->add($i18n->get('error.field'), $i18n->get('label.manager_login')); + if (ttUserHelper::getUserByLogin($cl_manager_login)) + $err->add($i18n->get('error.user_exists')); + if (!$auth->isPasswordExternal()) { + if (!ttValidString($cl_password1)) + $err->add($i18n->get('error.field'), $i18n->get('label.password')); + if (!ttValidString($cl_password2)) + $err->add($i18n->get('error.field'), $i18n->get('label.confirm_password')); + if ($cl_password1 !== $cl_password2) + $err->add($i18n->get('error.not_equal'), $i18n->get('label.password'), $i18n->get('label.confirm_password')); + } + if (!ttValidEmail($cl_manager_email, true)) + $err->add($i18n->get('error.field'), $i18n->get('label.email')); + if (!ttUserHelper::canAdd()) + $err->add($i18n->get('error.user_count')); + + if (!defined('CURRENCY_DEFAULT')) define('CURRENCY_DEFAULT', '$'); + + if ($err->no()) { + if (ttAdmin::createOrg(array('group_name' => $cl_group_name, + 'currency' => CURRENCY_DEFAULT, + 'lang' => $cl_lang, + 'user_name' => $cl_manager_name, + 'login' => $cl_manager_login, + 'password' => $cl_password1, + 'email' => $cl_manager_email))) { + header('Location: admin_groups.php'); + exit(); + } else { + $err->add($i18n->get('error.db')); + } + } +} // isPost + +$smarty->assign('auth_external', $auth->isPasswordExternal()); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.groupForm.group_name.focus()"'); +$smarty->assign('content_page_name', 'admin_group_add.tpl'); +$smarty->assign('title', $i18n->get('title.add_group')); +$smarty->display('index.tpl'); diff --git a/admin_group_delete.php b/admin_group_delete.php new file mode 100644 index 000000000..909b2347a --- /dev/null +++ b/admin_group_delete.php @@ -0,0 +1,46 @@ +getParameter('id'); +$group_name = ttAdmin::getGroupName($group_id); +if (!($group_id && $group_name != null)) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$form = new Form('groupForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$group_id)); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); + +if ($request->isPost()) { + if ($request->getParameter('btn_delete')) { + if (ttAdmin::markGroupDeleted($group_id)) { + header('Location: admin_groups.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } + + if ($request->getParameter('btn_cancel')) { + header('Location: admin_groups.php'); + exit(); + } +} // isPost + +$smarty->assign('group_to_delete', $group_name); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.delete_group')); +$smarty->assign('content_page_name', 'admin_group_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/admin_group_edit.php b/admin_group_edit.php new file mode 100644 index 000000000..496c7b8fb --- /dev/null +++ b/admin_group_edit.php @@ -0,0 +1,114 @@ +getParameter('id'); +$group_name = ttAdmin::getGroupName($group_id); +if (!($group_id && $group_name != null)) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$org_details = ttAdmin::getOrgDetails($group_id); +if (!$org_details) $err->add($i18n->get('error.db')); + +if ($request->isPost()) { + $cl_group_name = trim($request->getParameter('group_name')); + $cl_manager_name = trim($request->getParameter('manager_name')); + $cl_manager_login = trim($request->getParameter('manager_login')); + if (!$auth->isPasswordExternal()) { + $cl_password1 = $request->getParameter('password1'); + $cl_password2 = $request->getParameter('password2'); + } + $cl_manager_email = trim($request->getParameter('manager_email')); +} else { + $cl_group_name = $org_details['group_name']; + $cl_manager_name = $org_details['manager_name']; + $cl_manager_login = $org_details['manager_login']; + if (!$auth->isPasswordExternal()) { + $cl_password1 = $cl_password2 = ''; + } + $cl_manager_email = $org_details['manager_email']; +} + +$form = new Form('groupForm'); +$form->addInput(array('type'=>'text','maxlength'=>'80','name'=>'group_name','value'=>$cl_group_name)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_name','value'=>$cl_manager_name)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_login','value'=>$cl_manager_login)); +if (!$auth->isPasswordExternal()) { + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'password1','value'=>$cl_password1)); + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'password2','value'=>$cl_password2)); +} +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_email','value'=>$cl_manager_email)); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$group_id)); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); + +if ($request->isPost()) { + if ($request->getParameter('btn_save')) { + + // Validate user input. + if (!ttValidString($cl_group_name)) + $err->add($i18n->get('error.field'), $i18n->get('label.group_name')); + if (!ttValidString($cl_manager_name)) + $err->add($i18n->get('error.field'), $i18n->get('label.manager_name')); + if (!ttValidString($cl_manager_login)) + $err->add($i18n->get('error.field'), $i18n->get('label.manager_login')); + // If we change login, it must be unique. + if ($cl_manager_login != $org_details['manager_login']) { + if (ttUserHelper::getUserByLogin($cl_manager_login)) { + $err->add($i18n->get('error.user_exists')); + } + } + if (!$auth->isPasswordExternal() && ($cl_password1 || $cl_password2)) { + if (!ttValidString($cl_password1)) + $err->add($i18n->get('error.field'), $i18n->get('label.password')); + if (!ttValidString($cl_password2)) + $err->add($i18n->get('error.field'), $i18n->get('label.confirm_password')); + if ($cl_password1 !== $cl_password2) + $err->add($i18n->get('error.not_equal'), $i18n->get('label.password'), $i18n->get('label.confirm_password')); + } + if (!ttValidEmail($cl_manager_email, true)) + $err->add($i18n->get('error.field'), $i18n->get('label.email')); + + if ($err->no()) { + if (ttAdmin::updateGroup(array('group_id' => $group_id, + 'old_group_name' => $org_details['group_name'], + 'new_group_name' => $cl_group_name, + 'user_id' => $org_details['manager_id'], + 'user_name' => $cl_manager_name, + 'old_login' => $org_details['manager_login'], + 'new_login' => $cl_manager_login, + 'password1' => $cl_password1, + 'password2' => $cl_password2, + 'email' => $cl_manager_email))) { + header('Location: admin_groups.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } + } + + if ($request->getParameter('btn_cancel')) { + header('Location: admin_groups.php'); + exit(); + } +} // isPost + +$smarty->assign('auth_external', $auth->isPasswordExternal()); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.groupForm.manager_name.focus()"'); +$smarty->assign('title', $i18n->get('title.edit_group')); +$smarty->assign('content_page_name', 'admin_group_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/admin_groups.php b/admin_groups.php new file mode 100644 index 000000000..8dffd3931 --- /dev/null +++ b/admin_groups.php @@ -0,0 +1,18 @@ +assign('groups', ttOrgHelper::getOrgs()); +$smarty->assign('title', $i18n->get('title.groups')); +$smarty->assign('content_page_name', 'admin_groups.tpl'); +$smarty->display('index.tpl'); diff --git a/admin_options.php b/admin_options.php index 5431b22aa..aed852320 100644 --- a/admin_options.php +++ b/admin_options.php @@ -1,41 +1,20 @@ isPost()) { $cl_name = trim($request->getParameter('name')); $cl_login = trim($request->getParameter('login')); @@ -54,45 +33,44 @@ $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login)); if (!$auth->isPasswordExternal()) { - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password1','aspassword'=>true,'value'=>$cl_password1)); - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password2','aspassword'=>true,'value'=>$cl_password2)); + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'password1','value'=>$cl_password1)); + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'password2','value'=>$cl_password2)); } $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','value'=>$cl_email)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.submit'))); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.submit'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name')); - if (!ttValidString($cl_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); - // New login must be unique. + if (!ttValidString($cl_name)) + $err->add($i18n->get('error.field'), $i18n->get('label.person_name')); + if (!ttValidString($cl_login)) + $err->add($i18n->get('error.field'), $i18n->get('label.login')); + // If we change login, it must be unique. if ($cl_login != $user->login && ttUserHelper::getUserByLogin($cl_login)) - $err->add($i18n->getKey('error.user_exists')); - if (!$auth->isPasswordExternal() && ($cl_password1 || $cl_password2)) { - if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); - if ($cl_password1 !== $cl_password2) - $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } - if (!ttValidEmail($cl_email, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - // Finished validating user input. - - if ($err->no()) { - if (ttUserHelper::update($user->id, array( - 'name' => $cl_name, - 'login' => $cl_login, - 'password' => $cl_password1, - 'email' => $cl_email, - 'status' => ACTIVE))) { - header('Location: admin_teams.php'); - exit(); - } else { - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.user_exists')); + if (!$auth->isPasswordExternal() && ($cl_password1 != null || $cl_password2 != null)) { + if (!ttValidString($cl_password1)) + $err->add($i18n->get('error.field'), $i18n->get('label.password')); + if (!ttValidString($cl_password2)) + $err->add($i18n->get('error.field'), $i18n->get('label.confirm_password')); + if ($cl_password1 !== $cl_password2) + $err->add($i18n->get('error.not_equal'), $i18n->get('label.password'), $i18n->get('label.confirm_password')); } + if (!ttValidEmail($cl_email, true)) + $err->add($i18n->get('error.field'), $i18n->get('label.email')); + + if ($err->no() && ttAdmin::updateSelf(array('name' => $cl_name, + 'login' => $cl_login, + 'password1' => $cl_password1, + 'password2' => $cl_password2, + 'email' => $cl_email))) { + header('Location: admin_groups.php'); + exit(); } } // isPost $smarty->assign('auth_external', $auth->isPasswordExternal()); $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.options')); +$smarty->assign('title', $i18n->get('title.options')); $smarty->assign('content_page_name', 'admin_options.tpl'); $smarty->display('index.tpl'); diff --git a/admin_team_add.php b/admin_team_add.php deleted file mode 100644 index d17f864f1..000000000 --- a/admin_team_add.php +++ /dev/null @@ -1,103 +0,0 @@ -isPost()) { - $cl_team_name = trim($request->getParameter('team_name')); - $cl_manager_name = trim($request->getParameter('manager_name')); - $cl_manager_login = trim($request->getParameter('manager_login')); - if (!$auth->isPasswordExternal()) { - $cl_password1 = $request->getParameter('password1'); - $cl_password2 = $request->getParameter('password2'); - } - $cl_manager_email = trim($request->getParameter('manager_email')); -} - -$form = new Form('teamForm'); -$form->addInput(array('type'=>'text','maxlength'=>'200','name'=>'team_name','value'=>$cl_team_name)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_name','value'=>$cl_manager_name)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_login','value'=>$cl_manager_login)); -if (!$auth->isPasswordExternal()) { - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password1','aspassword'=>true,'value'=>$cl_password1)); - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password2','aspassword'=>true,'value'=>$cl_password2)); -} -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_email','value'=>$cl_manager_email)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.submit'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_team_name, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.team_name')); - if (!ttValidString($cl_manager_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_name')); - if (!ttValidString($cl_manager_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_login')); - if (!$auth->isPasswordExternal()) { - if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); - if ($cl_password1 !== $cl_password2) - $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } - if (!ttValidEmail($cl_manager_email, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - - if ($err->no()) { - if (!ttUserHelper::getUserByLogin($cl_manager_login)) { - // Create a new team. - if (!defined('CURRENCY_DEFAULT')) define('CURRENCY_DEFAULT', '$'); - $team_id = ttTeamHelper::insert(array('name'=>$cl_team_name,'currency'=>CURRENCY_DEFAULT)); - if ($team_id) { - // Team created, now create a team manager. - $user_id = ttUserHelper::insert(array( - 'team_id' => $team_id, - 'role' => ROLE_MANAGER, - 'name' => $cl_manager_name, - 'login' => $cl_manager_login, - 'password' => $cl_password1, - 'email' => $cl_manager_email)); - } - if ($team_id && $user_id) { - header('Location: admin_teams.php'); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.user_exists')); - } -} // isPost - -$smarty->assign('auth_external', $auth->isPasswordExternal()); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.teamForm.team.focus()"'); -$smarty->assign('content_page_name', 'admin_team_add.tpl'); -$smarty->assign('title', $i18n->getKey('title.create_team')); -$smarty->display('index.tpl'); diff --git a/admin_team_delete.php b/admin_team_delete.php deleted file mode 100644 index 987543c58..000000000 --- a/admin_team_delete.php +++ /dev/null @@ -1,67 +0,0 @@ -getParameter('id'); -$team_details = ttTeamHelper::getTeamDetails($team_id); -$team_name = $team_details['team_name']; - -$form = new Form('teamForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$team_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); - -if ($request->isPost()) { - if ($request->getParameter('btn_delete')) { - if (ttTeamHelper::markDeleted($team_id)) { - header('Location: admin_teams.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } - - if ($request->getParameter('btn_cancel')) { - header('Location: admin_teams.php'); - exit(); - } -} // isPost - -$smarty->assign('team_to_delete', $team_name); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.delete_team')); -$smarty->assign('content_page_name', 'admin_team_delete.tpl'); -$smarty->display('index.tpl'); diff --git a/admin_team_edit.php b/admin_team_edit.php deleted file mode 100644 index 09ef866da..000000000 --- a/admin_team_edit.php +++ /dev/null @@ -1,122 +0,0 @@ -getParameter('id'); -$team_details = ttTeamHelper::getTeamDetails($team_id); - -if ($request->isPost()) { - $cl_team_name = trim($request->getParameter('team_name')); - $cl_manager_name = trim($request->getParameter('manager_name')); - $cl_manager_login = trim($request->getParameter('manager_login')); - if (!$auth->isPasswordExternal()) { - $cl_password1 = $request->getParameter('password1'); - $cl_password2 = $request->getParameter('password2'); - } - $cl_manager_email = trim($request->getParameter('manager_email')); -} else { - $cl_team_name = $team_details['team_name']; - $cl_manager_name = $team_details['manager_name']; - $cl_manager_login = $team_details['manager_login']; - if (!$auth->isPasswordExternal()) { - $cl_password1 = $cl_password2 = ''; - } - $cl_manager_email = $team_details['manager_email']; -} - -$form = new Form('teamForm'); -$form->addInput(array('type'=>'text','maxlength'=>'80','name'=>'team_name','value'=>$cl_team_name)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_name','value'=>$cl_manager_name)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_login','value'=>$cl_manager_login)); -if (!$auth->isPasswordExternal()) { - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password1','aspassword'=>true,'value'=>$cl_password1)); - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password2','aspassword'=>true,'value'=>$cl_password2)); -} -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_email','value'=>$cl_manager_email)); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$team_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); - -if ($request->isPost()) { - if ($request->getParameter('btn_save')) { - // Validate user input. - if (!ttValidString($cl_team_name, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.team_name')); - if (!ttValidString($cl_manager_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_name')); - if (!ttValidString($cl_manager_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_login')); - if (!$auth->isPasswordExternal() && ($cl_password1 || $cl_password2)) { - if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); - if ($cl_password1 !== $cl_password2) - $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } - if (!ttValidEmail($cl_manager_email, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - - // New login must be unique. - if ($cl_manager_login != $team_details['manager_login']) - if (ttUserHelper::getUserByLogin($cl_manager_login)) $err->add($i18n->getKey('error.user_exists')); - - if ($err->no()) { - $update_result = ttTeamHelper::update($team_id, array('name'=>$cl_team_name)); - if ($update_result) { - $update_result = ttUserHelper::update($team_details['manager_id'], array( - 'name' => $cl_manager_name, - 'login' => $cl_manager_login, - 'password' => $cl_password1, - 'email' => $cl_manager_email, - 'status' => ACTIVE)); - } - if ($update_result) { - header('Location: admin_teams.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } - } - - if ($request->getParameter('btn_cancel')) { - header('Location: admin_teams.php'); - exit(); - } -} // isPost - -$smarty->assign('auth_external', $auth->isPasswordExternal()); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.teamForm.manager_name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.edit_team')); -$smarty->assign('content_page_name', 'admin_team_edit.tpl'); -$smarty->display('index.tpl'); diff --git a/admin_teams.php b/admin_teams.php deleted file mode 100644 index a1f848a4f..000000000 --- a/admin_teams.php +++ /dev/null @@ -1,42 +0,0 @@ -assign('teams', ttTeamHelper::getTeams()); -$smarty->assign('title', $i18n->getKey('title.teams')); -$smarty->assign('content_page_name', 'admin_teams.tpl'); -$smarty->display('index.tpl'); diff --git a/cf_custom_field_add.php b/cf_custom_field_add.php index 1d293d377..87d46bcd5 100644 --- a/cf_custom_field_add.php +++ b/cf_custom_field_add.php @@ -1,74 +1,60 @@ isPluginEnabled('cf')) { +// Access checks. +if (!ttAccessAllowed('manage_custom_fields')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('cf')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. if ($request->isPost()) { $cl_field_name = trim($request->getParameter('name')); - $cl_field_type = $request->getParameter('type'); - $cl_required = $request->getParameter('required'); - if (!$cl_required) - $cl_required = 0; + $cl_entity_type = (int)$request->getParameter('entity'); + $cl_field_type = (int)$request->getParameter('type'); + $cl_required = (int)$request->getParameter('required'); } $form = new Form('fieldForm'); $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>'')); +$form->addInput(array('type'=>'combobox','name'=>'entity', + 'data'=>array(CustomFields::ENTITY_TIME=>$i18n->get('entity.time'), + CustomFields::ENTITY_USER=>$i18n->get('entity.user'), + CustomFields::ENTITY_PROJECT=>$i18n->get('entity.project')) +)); $form->addInput(array('type'=>'combobox','name'=>'type', - 'data'=>array(CustomFields::TYPE_TEXT=>$i18n->getKey('label.type_text'), - CustomFields::TYPE_DROPDOWN=>$i18n->getKey('label.type_dropdown')) + 'data'=>array(CustomFields::TYPE_TEXT=>$i18n->get('label.type_text'), + CustomFields::TYPE_DROPDOWN=>$i18n->get('label.type_dropdown')) )); -$form->addInput(array('type'=>'checkbox','name'=>'required','data'=>1,'value'=>'0')); -$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add'))); +$form->addInput(array('type'=>'checkbox','name'=>'required')); +$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_field_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); + if (!ttValidString($cl_field_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (CustomFields::getFieldByName($cl_field_name, $cl_entity_type) != null) $err->add($i18n->get('error.object_exists')); if ($err->no()) { - $res = CustomFields::insertField($cl_field_name, $cl_field_type, $cl_required); + $res = CustomFields::insertField($cl_field_name, $cl_entity_type, $cl_field_type, $cl_required); if ($res) { header('Location: cf_custom_fields.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.fieldForm.name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.cf_add_custom_field')); +$smarty->assign('title', $i18n->get('title.cf_add_custom_field')); $smarty->assign('content_page_name', 'cf_custom_field_add.tpl'); $smarty->display('index.tpl'); diff --git a/cf_custom_field_delete.php b/cf_custom_field_delete.php index 88779bedb..ba222c657 100644 --- a/cf_custom_field_delete.php +++ b/cf_custom_field_delete.php @@ -1,42 +1,27 @@ isPluginEnabled('cf')) { +// Access checks. +if (!ttAccessAllowed('manage_custom_fields')) { header('Location: access_denied.php'); exit(); } - -$id = $request->getParameter('id'); +if (!$user->isPluginEnabled('cf')) { + header('Location: feature_disabled.php'); + exit(); +} +$id = (int)$request->getParameter('id'); +$field = CustomFields::getField($id); +if (!$field) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. $form = new Form('fieldDeleteForm'); @@ -48,7 +33,7 @@ header('Location: cf_custom_fields.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } if ($request->getParameter('btn_cancel')) { // Cancel button pressed. @@ -56,20 +41,14 @@ exit(); } } else { - $field = CustomFields::getField($id); - if (false === $field) - $err->add($i18n->getKey('error.db')); - - if ($err->no()) { - $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$id)); - $form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); - $form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); - } + $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$id)); + $form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); + $form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); } $smarty->assign('field', $field['label']); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.fieldDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->getKey('title.cf_delete_custom_field')); +$smarty->assign('title', $i18n->get('title.cf_delete_custom_field')); $smarty->assign('content_page_name', 'cf_custom_field_delete.tpl'); $smarty->display('index.tpl'); diff --git a/cf_custom_field_edit.php b/cf_custom_field_edit.php index b8e0f8ece..3f959ee22 100644 --- a/cf_custom_field_edit.php +++ b/cf_custom_field_edit.php @@ -1,55 +1,48 @@ isPluginEnabled('cf')) { +// Access checks. +if (!ttAccessAllowed('manage_custom_fields')) { header('Location: access_denied.php'); exit(); } - -$cl_id = $request->getParameter('id'); +if (!$user->isPluginEnabled('cf')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_id = (int)$request->getParameter('id'); $field = CustomFields::getField($cl_id); -if (false === $field) - $err->add($i18n->getKey('error.db')); +if (!$field) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. $form = new Form('fieldForm'); if ($err->no()) { $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$field['label'])); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); - $form->addInput(array('type'=>'checkbox','name'=>'required','data'=>1,'value'=>$field['required'])); + + // TODO: consider encapsulating this block in a function. + $entity_type = $field['entity_type']; + if (CustomFields::ENTITY_TIME == $entity_type) + $entity = $i18n->get('entity.time'); + else if (CustomFields::ENTITY_USER == $entity_type) + $entity = $i18n->get('entity.user'); + else if (CustomFields::ENTITY_PROJECT == $entity_type) + $entity = $i18n->get('entity.project'); + $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'entity','value'=>$entity,'enable'=>false)); + $form->addInput(array('type'=>'combobox','name'=>'type','value'=>$field['type'], - 'data'=>array(CustomFields::TYPE_TEXT=>$i18n->getKey('label.type_text'), - CustomFields::TYPE_DROPDOWN=>$i18n->getKey('label.type_dropdown')))); - $form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); + 'data'=>array(CustomFields::TYPE_TEXT=>$i18n->get('label.type_text'), + CustomFields::TYPE_DROPDOWN=>$i18n->get('label.type_dropdown')))); + $form->addInput(array('type'=>'checkbox','name'=>'required','value'=>$field['required'])); + $form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); } if ($request->isPost()) { @@ -60,7 +53,7 @@ $cl_required = 0; // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); if ($err->no()) { $res = CustomFields::updateField($cl_id, $cl_name, $cl_type, $cl_required); @@ -68,12 +61,12 @@ header('Location: cf_custom_fields.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.fieldForm.name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.cf_edit_custom_field')); +$smarty->assign('title', $i18n->get('title.cf_edit_custom_field')); $smarty->assign('content_page_name', 'cf_custom_field_edit.tpl'); $smarty->display('index.tpl'); diff --git a/cf_custom_fields.php b/cf_custom_fields.php index 18de06059..6552bb7f6 100644 --- a/cf_custom_fields.php +++ b/cf_custom_fields.php @@ -1,40 +1,21 @@ isPluginEnabled('cf')) { +// Access checks. +if (!ttAccessAllowed('manage_custom_fields')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('cf')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. $form = new Form('customFieldsForm'); @@ -45,16 +26,13 @@ exit(); } } else { - $form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add'))); + $form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); $fields = CustomFields::getFields(); - // At this time only one custom field is supported. Disable the Add button if we already have one or more custom fields. - if (count($fields) > 0) - $form->getElement('btn_add')->setEnable(false); } $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('custom_fields', $fields); -$smarty->assign('title', $i18n->getKey('title.cf_custom_fields')); +$smarty->assign('title', $i18n->get('title.cf_custom_fields')); $smarty->assign('content_page_name', 'cf_custom_fields.tpl'); $smarty->display('index.tpl'); diff --git a/cf_dropdown_option_add.php b/cf_dropdown_option_add.php index ecf4892d6..582794ae5 100644 --- a/cf_dropdown_option_add.php +++ b/cf_dropdown_option_add.php @@ -1,58 +1,40 @@ isPluginEnabled('cf')) { +// Access checks. +if (!ttAccessAllowed('manage_custom_fields')) { header('Location: access_denied.php'); exit(); } - -$cl_field_id = $request->getParameter('field_id'); +if (!$user->isPluginEnabled('cf')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_field_id = (int)$request->getParameter('field_id'); $field = CustomFields::getField($cl_field_id); -if (false === $field) - $err->add($i18n->getKey('error.db')); +if (!$field) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. $form = new Form('optionAddForm'); if ($err->no()) { $form->addInput(array('type'=>'hidden','name'=>'field_id','value'=>$cl_field_id)); - $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>'')); - $form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add'))); + $form->addInput(array('type'=>'text','maxlength'=>'32','name'=>'name','value'=>'')); + $form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); } if ($request->isPost()) { $cl_option_name = trim($request->getParameter('name')); // Validate user input. - if (!ttValidString($cl_option_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); + if (!ttValidString($cl_option_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); if ($err->no()) { $res = CustomFields::insertOption($cl_field_id, $cl_option_name); @@ -60,12 +42,12 @@ header("Location: cf_dropdown_options.php?field_id=$cl_field_id"); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.optionAddForm.name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.cf_add_dropdown_option')); +$smarty->assign('title', $i18n->get('title.cf_add_dropdown_option')); $smarty->assign('content_page_name', 'cf_dropdown_option_add.tpl'); $smarty->display('index.tpl'); diff --git a/cf_dropdown_option_delete.php b/cf_dropdown_option_delete.php index ec06b49fd..50c685014 100644 --- a/cf_dropdown_option_delete.php +++ b/cf_dropdown_option_delete.php @@ -1,42 +1,28 @@ isPluginEnabled('cf')) { +// Access checks. +if (!ttAccessAllowed('manage_custom_fields')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('cf')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_id = (int)$request->getParameter('id'); +$option = CustomFields::getOptionName($cl_id); +if ($option == null) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. -$cl_id = $request->getParameter('id'); $form = new Form('optionDeleteForm'); if ($request->isPost()) { @@ -50,7 +36,7 @@ header("Location: cf_dropdown_options.php?field_id=$field_id"); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } if ($request->getParameter('btn_cancel')) { // Cancel button pressed. @@ -58,20 +44,14 @@ exit(); } } else { - $option = CustomFields::getOptionName($cl_id); - if (false === $option) - $err->add($i18n->getKey('error.db')); - - if ($err->no()) { - $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); - $form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); - $form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); - } + $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); + $form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); + $form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); } $smarty->assign('option', $option); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.optionDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->getKey('title.cf_delete_dropdown_option')); +$smarty->assign('title', $i18n->get('title.cf_delete_dropdown_option')); $smarty->assign('content_page_name', 'cf_dropdown_option_delete.tpl'); $smarty->display('index.tpl'); diff --git a/cf_dropdown_option_edit.php b/cf_dropdown_option_edit.php index ba486ae98..2477b057c 100644 --- a/cf_dropdown_option_edit.php +++ b/cf_dropdown_option_edit.php @@ -1,58 +1,40 @@ isPluginEnabled('cf')) { +// Access checks. +if (!ttAccessAllowed('manage_custom_fields')) { header('Location: access_denied.php'); exit(); } - -$cl_id = $request->getParameter('id'); +if (!$user->isPluginEnabled('cf')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_id = (int)$request->getParameter('id'); $cl_name = CustomFields::getOptionName($cl_id); -if (false === $cl_name) - $err->add($i18n->getKey('error.db')); +if ($cl_name == null) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. $form = new Form('optionEditForm'); if ($err->no()) { - $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); + $form->addInput(array('type'=>'text','maxlength'=>'32','name'=>'name','value'=>$cl_name)); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); - $form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); + $form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); } if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); if ($err->no()) { $res = CustomFields::updateOption($cl_id, $cl_name); @@ -62,12 +44,12 @@ header("Location: cf_dropdown_options.php?field_id=$field_id"); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.optionEditForm.name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.cf_edit_dropdown_option')); +$smarty->assign('title', $i18n->get('title.cf_edit_dropdown_option')); $smarty->assign('content_page_name', 'cf_dropdown_option_edit.tpl'); $smarty->display('index.tpl'); diff --git a/cf_dropdown_options.php b/cf_dropdown_options.php index 544eefdbe..06f53a7d8 100644 --- a/cf_dropdown_options.php +++ b/cf_dropdown_options.php @@ -1,51 +1,32 @@ isPluginEnabled('cf')) { +// Access checks. +if (!ttAccessAllowed('manage_custom_fields')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('cf')) { + header('Location: feature_disabled.php'); + exit(); +} +$field_id = (int)$request->getParameter('field_id'); +$field = CustomFields::getField($field_id); +if (!$field) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. -$field_id = $request->getParameter('field_id'); $options = CustomFields::getOptions($field_id); -if (false === $options) - $err->add($i18n->getKey('error.db')); - -$form = new Form('dropdownOptionsForm'); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('field_id', $field_id); $smarty->assign('options', $options); -$smarty->assign('title', $i18n->getKey('title.cf_dropdown_options')); +$smarty->assign('title', $i18n->get('title.cf_dropdown_options')); $smarty->assign('content_page_name', 'cf_dropdown_options.tpl'); $smarty->display('index.tpl'); diff --git a/charts.php b/charts.php index 3ef76e528..9dbb95c70 100644 --- a/charts.php +++ b/charts.php @@ -1,183 +1,212 @@ isPluginEnabled('ch')) { +define('ALL_USERS_OPTION_ID', -1); // An identifier for "all users" seclection in User dropdown. + +// Access checks. +if (!(ttAccessAllowed('view_own_charts') || ttAccessAllowed('view_charts') || ttAccessAllowed('view_all_charts'))) { + header('Location: access_denied.php'); + exit(); +} +if (!$user->isPluginEnabled('ch')) { + header('Location: feature_disabled.php'); + exit(); +} +if (!$user->exists()) { + header('Location: access_denied.php'); // Nobody to display a chart for. + exit(); +} +if ($user->behalf_id && (!($user->can('view_charts') || $user->can('view_all_charts')) || !$user->checkBehalfId())) { + header('Location: access_denied.php'); // Trying on behalf, but no right or wrong user. + exit(); +} +if (!$user->behalf_id && !$user->can('view_own_charts') && !$user->adjustBehalfId()) { + header('Location: access_denied.php'); // Trying as self, but no right for self, and noone to view on behalf. + exit(); +} +$userDropdownSelectionId = (int)$request->getParameter('user'); // Reused below access checks. +if ($request->isPost() && $request->getParameter('user')) { + if ($userDropdownSelectionId == constant('ALL_USERS_OPTION_ID') && !ttAccessAllowed('view_all_charts')) { + header('Location: access_denied.php'); // All users option is only for users with view_all_charts access right. + exit(); + } + if ($userDropdownSelectionId != constant('ALL_USERS_OPTION_ID') && !$user->isUserValid($userDropdownSelectionId)) { + header('Location: access_denied.php'); // Wrong user id on post. + exit(); + } +} +$date = $request->getParameter('date'); +if ($date && !ttValidDbDateFormatDate($date)) { header('Location: access_denied.php'); exit(); } +// End of access checks. + +// Determine user for whom we display this page. +$userChanged = (int)$request->getParameter('user_changed'); +if ($request->isPost() && $userChanged ) { + if ($userDropdownSelectionId != constant('ALL_USERS_OPTION_ID')) { + $user->setOnBehalfUser($userDropdownSelectionId); + } +} +if ($request->isGet()) { + $userDropdownSelectionId = $user->getUser(); + // Note that this may change to ALL_USERS_OPTION_ID below from session. +} + +$uc = new ttUserConfig(); +$tracking_mode = $user->getTrackingMode(); // Initialize and store date in session. $cl_date = $request->getParameter('date', @$_SESSION['date']); if(!$cl_date) { - $now = new DateAndTime(DB_DATEFORMAT); - $cl_date = $now->toString(DB_DATEFORMAT); + $now = new ttDate(); + $cl_date = $now->toString(); } $_SESSION['date'] = $cl_date; -// Initialize chart interval. -$cl_interval = $_SESSION['chart_interval']; -if (!$cl_interval) { - $sc = new ttSysConfig($user->id); - $cl_interval = $sc->getValue(SYSC_CHART_INTERVAL); -} -if (!$cl_interval) $cl_interval = INTERVAL_THIS_MONTH; -$_SESSION['chart_interval'] = $cl_interval; - -// Initialize chart type. -$cl_type = $_SESSION['chart_type']; -if (!$cl_type) { - $sc = new ttSysConfig($user->id); - $cl_type = $sc->getValue(SYSC_CHART_TYPE); -} -if (MODE_TIME == $user->tracking_mode) { - if ($user->isPluginEnabled('cl')) - $cl_type = CHART_CLIENTS; +if ($request->isPost()) { + $cl_fav_report = (int)$request->getParameter('favorite_report'); + if (!$cl_fav_report) $cl_fav_report = -1; + $_SESSION['fav_report'] = $cl_fav_report; + + $cl_type = (int)$request->getParameter('type'); + if (!$cl_type) $cl_type = ttChartHelper::adjustType($cl_type); + $_SESSION['chart_type'] = $cl_type; + $uc->setValue(SYSC_CHART_TYPE, $cl_type); + + // Remember all users selection in session. + $_SESSION['chart_all_users'] = $userDropdownSelectionId == constant('ALL_USERS_OPTION_ID') ? true : false; + + $cl_interval = (int)$request->getParameter('interval'); + if (!$cl_interval) $cl_interval = INTERVAL_THIS_MONTH; + $_SESSION['chart_interval'] = $cl_interval; + $uc->setValue(SYSC_CHART_INTERVAL, $cl_interval); } else { - if ($cl_type == CHART_CLIENTS) { - if (!$user->isPluginEnabled('cl')) - $cl_type = CHART_PROJECTS; - } elseif ($cl_type == CHART_TASKS) { - if (MODE_PROJECTS_AND_TASKS != $user->tracking_mode) - $cl_type = CHART_PROJECTS; - } + // Initialize fav report selector. + $cl_fav_report = @$_SESSION['fav_report']; + if (!$cl_fav_report) $cl_fav_report = -1; + $_SESSION['fav_report'] = $cl_fav_report; + + // Initialize chart type. + $cl_type = @$_SESSION['chart_type']; + if (!$cl_type) $cl_type = $uc->getValue(SYSC_CHART_TYPE); + $cl_type = ttChartHelper::adjustType($cl_type); + $_SESSION['chart_type'] = $cl_type; + + // Set user selection to all users, if necessary. + $allUsersSetInSession = @$_SESSION['chart_all_users']; + if ($allUsersSetInSession) + $userDropdownSelectionId = constant('ALL_USERS_OPTION_ID'); + + // Initialize chart interval. + $cl_interval = @$_SESSION['chart_interval']; + if (!$cl_interval) $cl_interval = $uc->getValue(SYSC_CHART_INTERVAL); + if (!$cl_interval) $cl_interval = INTERVAL_THIS_MONTH; + $_SESSION['chart_interval'] = $cl_interval; } -if (!$cl_type) $cl_type = CHART_PROJECTS; -$_SESSION['chart_type'] = $cl_type; - -// Who do we draw charts for? -$on_behalf_id = $request->getParameter('onBehalfUser', (isset($_SESSION['behalf_id'])? $_SESSION['behalf_id'] : $user->id)); - -if ($request->getMethod( )== 'POST') { - // If chart interval changed - save it. - $cl_interval = $request->getParameter('interval'); - if ($cl_interval) { - // Save in the session - $_SESSION['chart_interval'] = $cl_interval; - // and permanently. - $sc = new ttSysConfig($user->id); - $sc->setValue(SYSC_CHART_INTERVAL, $cl_interval); - } - // If chart type changed - save it. - $cl_type = $request->getParameter('type'); - if ($cl_type) { - // Save in the session - $_SESSION['chart_type'] = $cl_type; - // and permanently. - $sc = new ttSysConfig($user->id); - $sc->setValue(SYSC_CHART_TYPE, $cl_type); - } - // If user has changed - set behalf_id accordingly in the session. - if ($request->getParameter('onBehalfUser')) { - if($user->canManageTeam()) { - unset($_SESSION['behalf_id']); - unset($_SESSION['behalf_name']); - - if($on_behalf_id != $user->id) { - $_SESSION['behalf_id'] = $on_behalf_id; - $_SESSION['behalf_name'] = ttUserHelper::getUserName($on_behalf_id); - } - header('Location: charts.php'); - exit(); - } - } -} // isPost // Elements of chartForm. $chart_form = new Form('chartForm'); +$largeScreenCalendarRowSpan = 1; // Number of rows calendar spans on large screens. + +// Fav report control. +$report_list = ttFavReportHelper::getReports(); +$chart_form->addInput(array('type'=>'combobox', + 'name'=>'favorite_report', + 'onchange'=>'handleFavReportSelection();this.form.submit();', + 'data'=>$report_list, + 'value' => $cl_fav_report, + 'datakeys'=>array('id','name'), + 'empty'=>array('-1'=>$i18n->get('dropdown.no')))); +$largeScreenCalendarRowSpan += 2; + +// Chart type options. +$chart_selector = (MODE_PROJECTS_AND_TASKS == $tracking_mode || $user->isPluginEnabled('cl')); +if ($chart_selector) { + $types = array(); + if (MODE_PROJECTS == $tracking_mode || MODE_PROJECTS_AND_TASKS == $tracking_mode) + $types[CHART_PROJECTS] = $i18n->get('dropdown.projects'); + if (MODE_PROJECTS_AND_TASKS == $tracking_mode) + $types[CHART_TASKS] = $i18n->get('dropdown.tasks'); + if ($user->isPluginEnabled('cl')) + $types[CHART_CLIENTS] = $i18n->get('dropdown.clients'); + + // Add chart type dropdown. + $chart_form->addInput(array('type' => 'combobox', + 'onchange' => 'this.form.submit();', + 'name' => 'type', + 'value' => $cl_type, + 'data' => $types + )); + $largeScreenCalendarRowSpan += 2; +} -// User dropdown. Changes the user "on behalf" of whom we are working. -if ($user->canManageTeam()) { - $user_list = ttTeamHelper::getActiveUsers(array('putSelfFirst'=>true)); - if (count($user_list) > 1) { +// User dropdown. Changes the user "on behalf" of whom we are working. +if ($user->can('view_charts') || $user->can('view_all_charts')) { + $rank = $user->getMaxRankForGroup($user->getGroup()); + if ($user->can('view_own_charts')) + $options = array('status'=>ACTIVE,'max_rank'=>$rank,'include_self'=>true,'self_first'=>true); + else + $options = array('status'=>ACTIVE,'max_rank'=>$rank); + $user_list = $user->getUsers($options); + // Add the --- all --- option to dropdown. + if ($user->can('view_all_charts')) { + $user_list[] = array('id'=>'-1','group_id'=>$user->getGroup(), 'name'=>$i18n->get('dropdown.all'), 'rights'=>''); + } + if (count($user_list) >= 1) { $chart_form->addInput(array('type'=>'combobox', - 'onchange'=>'this.form.submit();', - 'name'=>'onBehalfUser', - 'value'=>$on_behalf_id, + 'onchange'=>'this.form.user_changed.value=1;this.form.submit();', + 'name'=>'user', + 'value'=>$userDropdownSelectionId, 'data'=>$user_list, 'datakeys'=>array('id','name'), )); - $smarty->assign('on_behalf_control', 1); + $chart_form->addInput(array('type'=>'hidden','name'=>'user_changed')); + $largeScreenCalendarRowSpan += 2; + $smarty->assign('user_dropdown', 1); } } // Chart interval options. $intervals = array(); -$intervals[INTERVAL_THIS_DAY] = $i18n->getKey('dropdown.this_day'); -$intervals[INTERVAL_THIS_WEEK] = $i18n->getKey('dropdown.this_week'); -$intervals[INTERVAL_THIS_MONTH] = $i18n->getKey('dropdown.this_month'); -$intervals[INTERVAL_THIS_YEAR] = $i18n->getKey('dropdown.this_year'); -$intervals[INTERVAL_ALL_TIME] = $i18n->getKey('dropdown.all_time'); +$intervals[INTERVAL_THIS_DAY] = $i18n->get('dropdown.selected_day'); +$intervals[INTERVAL_THIS_WEEK] = $i18n->get('dropdown.selected_week'); +$intervals[INTERVAL_THIS_MONTH] = $i18n->get('dropdown.selected_month'); +$intervals[INTERVAL_PREVIOUS_MONTH] = $i18n->get('dropdown.previous_month'); +$intervals[INTERVAL_THIS_YEAR] = $i18n->get('dropdown.selected_year'); +$intervals[INTERVAL_ALL_TIME] = $i18n->get('dropdown.all_time'); // Chart interval dropdown. $chart_form->addInput(array('type' => 'combobox', - 'onchange' => 'if(this.form) this.form.submit();', + 'onchange' => 'this.form.submit();', 'name' => 'interval', 'value' => $cl_interval, 'data' => $intervals )); - -// Chart type options. -$chart_selector = (MODE_PROJECTS_AND_TASKS == $user->tracking_mode || $user->isPluginEnabled('cl')); -if ($chart_selector) { - $types = array(); - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) - $types[CHART_PROJECTS] = $i18n->getKey('dropdown.projects'); - if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) - $types[CHART_TASKS] = $i18n->getKey('dropdown.tasks'); - if ($user->isPluginEnabled('cl')) - $types[CHART_CLIENTS] = $i18n->getKey('dropdown.clients'); - - // Add chart type dropdown. - $chart_form->addInput(array('type' => 'combobox', - 'onchange' => 'if(this.form) this.form.submit();', - 'name' => 'type', - 'value' => $cl_type, - 'data' => $types - )); -} +$largeScreenCalendarRowSpan += 2; // Calendar. $chart_form->addInput(array('type'=>'calendar','name'=>'date','value'=>$cl_date)); // calendar // Get data for our chart. -$totals = ttChartHelper::getTotals($on_behalf_id, $cl_type, $cl_date, $cl_interval); +if ($cl_fav_report == -1) + $totals = ttChartHelper::getTotals($userDropdownSelectionId, $cl_type, $cl_date, $cl_interval); +else + $totals = ttChartHelper::getTotalsForFavReport($cl_fav_report, $cl_type); $smarty->assign('totals', $totals); // Prepare chart for drawing. @@ -199,20 +228,22 @@ foreach($totals as $total) { $data_set->addPoint(new Point( $total['name'], $total['time'])); } -$chart->setDataSet($data_set); +$chart->setDataSet($data_set); // Prepare a file name. $img_dir = TEMPLATE_DIR.'_c/'; // Directory. $file_name = uniqid('chart_').'.png'; // Short file name. Unique ID here is to avoid problems with browser caching. $img_ref = 'WEB-INF/templates_c/'.$file_name; // Image reference for html. -$file_name = $img_dir.$file_name; // Full file name. +$file_name = $img_dir.$file_name; // Full file name. // Clean up the file system from older images. $img_files = glob($img_dir.'chart_*.png'); -foreach($img_files as $file) { - // If the create time of file is older than 1 minute, delete it. - if (filemtime($file) < (time() - 60)) { - unlink($file); +if (is_array($img_files)) { + foreach($img_files as $file) { + // If file creation time is older than 1 minute, delete it. + if (filemtime($file) < (time() - 60)) { + unlink($file); + } } } @@ -220,9 +251,11 @@ $chart->renderEx(array('fileName'=>$file_name,'hideLogo'=>true,'hideTitle'=>true,'hideLabel'=>true)); // At this point libchart usage is complete and we have chart image on disk. +$smarty->assign('large_screen_calendar_row_span', $largeScreenCalendarRowSpan); $smarty->assign('img_file_name', $img_ref); $smarty->assign('chart_selector', $chart_selector); +$smarty->assign('onload', 'onLoad="adjustTodayLinks();handleFavReportSelection();"'); $smarty->assign('forms', array($chart_form->getName() => $chart_form->toArray())); -$smarty->assign('title', $i18n->getKey('title.charts')); +$smarty->assign('title', $i18n->get('title.charts')); $smarty->assign('content_page_name', 'charts.tpl'); $smarty->display('index.tpl'); diff --git a/client_add.php b/client_add.php index bd1516f16..f1f45333e 100644 --- a/client_add.php +++ b/client_add.php @@ -1,44 +1,26 @@ isPluginEnabled('cl')) { +// Access checks. +if (!ttAccessAllowed('manage_clients')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('cl')) { + header('Location: feature_disabled.php'); + exit(); +} -$projects = ttTeamHelper::getActiveProjects($user->team_id); +$projects = ttGroupHelper::getActiveProjects(); +$cl_name = $cl_address = $cl_tax = ''; +$cl_projects = array(); if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_address = trim($request->getParameter('address')); @@ -50,25 +32,25 @@ // $cl_projects[] = $project_item['id']; } +$show_projects = (MODE_PROJECTS == $user->getTrackingMode() || MODE_PROJECTS_AND_TASKS == $user->getTrackingMode()) && count($projects) > 0; + $form = new Form('clientForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 350px;','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','style'=>'width: 350px;','cols'=>'55','rows'=>'5','value'=>$cl_address)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','value'=>$cl_address)); $form->addInput(array('type'=>'floatfield','name'=>'tax','size'=>'10','format'=>'.2','value'=>$cl_tax)); -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) +if ($show_projects) $form->addInput(array('type'=>'checkboxgroup','name'=>'projects','data'=>$projects,'layout'=>'H','datakeys'=>array('id','name'),'value'=>$cl_projects)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.add'))); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.add'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_name')); - if (!ttValidString($cl_address, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_address')); - if (!ttValidFloat($cl_tax, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.tax')); + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.client_name')); + if (!ttValidString($cl_address, true)) $err->add($i18n->get('error.field'), $i18n->get('label.client_address')); + if (!ttValidFloat($cl_tax, true)) $err->add($i18n->get('error.field'), $i18n->get('label.tax')); if ($err->no()) { if (!ttClientHelper::getClientByName($cl_name)) { - if (ttClientHelper::insert(array( - 'team_id' => $user->team_id, - 'name' => $cl_name, + if (ttClientHelper::insert(array('name' => $cl_name, 'address' => $cl_address, 'tax' => $cl_tax, 'projects' => $cl_projects, @@ -76,14 +58,15 @@ header('Location: clients.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.client_exists')); + $err->add($i18n->get('error.object_exists')); } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.clientForm.name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.add_client')); +$smarty->assign('show_projects', $show_projects); +$smarty->assign('title', $i18n->get('title.add_client')); $smarty->assign('content_page_name', 'client_add.tpl'); $smarty->display('index.tpl'); diff --git a/client_delete.php b/client_delete.php index 767a919e9..e832c6bcb 100644 --- a/client_delete.php +++ b/client_delete.php @@ -1,64 +1,45 @@ isPluginEnabled('cl')) { +// Access checks. +if (!ttAccessAllowed('manage_clients')) { header('Location: access_denied.php'); exit(); } - +if (!$user->isPluginEnabled('cl')) { + header('Location: feature_disabled.php'); + exit(); +} $id = (int)$request->getParameter('id'); $client = ttClientHelper::getClient($id); +if (!$client) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. $client_to_delete = $client['name']; $form = new Form('clientDeleteForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$id)); $form->addInput(array('type'=>'combobox','name'=>'delete_client_entries', - 'data'=>array('0'=>$i18n->getKey('dropdown.do_not_delete'),'1'=>$i18n->getKey('dropdown.delete')))); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); + 'data'=>array('0'=>$i18n->get('dropdown.do_not_delete'),'1'=>$i18n->get('dropdown.delete')))); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); if ($request->isPost()) { - if(ttClientHelper::getClient($id)) { - if ($request->getParameter('btn_delete')) { - if (ttClientHelper::delete($id, $request->getParameter('delete_client_entries'))) { - header('Location: clients.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } - } else - $err->add($i18n->getKey('error.db')); + if ($request->getParameter('btn_delete')) { + if (ttClientHelper::delete($id, $request->getParameter('delete_client_entries'))) { + header('Location: clients.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } if ($request->getParameter('btn_cancel')) { header('Location: clients.php'); @@ -68,6 +49,6 @@ $smarty->assign('client_to_delete', $client_to_delete); $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.delete_client')); +$smarty->assign('title', $i18n->get('title.delete_client')); $smarty->assign('content_page_name', 'client_delete.tpl'); $smarty->display('index.tpl'); diff --git a/client_edit.php b/client_edit.php index 2b408c5da..665fda15e 100644 --- a/client_edit.php +++ b/client_edit.php @@ -1,46 +1,33 @@ isPluginEnabled('cl')) { +// Access checks. +if (!ttAccessAllowed('manage_clients')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('cl')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_id = (int)$request->getParameter('id'); +$client = ttClientHelper::getClient($cl_id, true); +if (!$client) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. -$cl_id = (int) $request->getParameter('id'); - -$projects = ttTeamHelper::getActiveProjects($user->team_id); - +$projects = ttGroupHelper::getActiveProjects(); +$cl_name = $cl_address = $cl_tax = $cl_status = null; +$cl_projects = array(); if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_address = trim($request->getParameter('address')); @@ -48,7 +35,6 @@ $cl_status = $request->getParameter('status'); $cl_projects = $request->getParameter('projects'); } else { - $client = ttClientHelper::getClient($cl_id, true); $cl_name = $client['name']; $cl_address = $client['address']; $cl_tax = $client['tax']; @@ -59,23 +45,26 @@ } } +$show_projects = (MODE_PROJECTS == $user->getTrackingMode() || MODE_PROJECTS_AND_TASKS == $user->getTrackingMode()) && count($projects) > 0; + $form = new Form('clientForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); -$form->addInput(array('type'=>'text','name'=>'name','maxlength'=>'100','style'=>'width: 350px;','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','style'=>'width: 350px;','cols'=>'55','rows'=>'5','value'=>$cl_address)); +$form->addInput(array('type'=>'text','name'=>'name','maxlength'=>'100','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','value'=>$cl_address)); $form->addInput(array('type'=>'floatfield','name'=>'tax','size'=>'10','format'=>'.2','value'=>$cl_tax)); $form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status, - 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive')))); -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) + 'data'=>array(ACTIVE=>$i18n->get('dropdown.status_active'),INACTIVE=>$i18n->get('dropdown.status_inactive')))); +if ($show_projects) $form->addInput(array('type'=>'checkboxgroup','name'=>'projects','data'=>$projects,'datakeys'=>array('id','name'),'layout'=>'H','value'=>$cl_projects)); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->getKey('button.copy'))); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); +$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->get('button.copy'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_name')); - if (!ttValidString($cl_address, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_address')); - if (!ttValidFloat($cl_tax, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.tax')); + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.client_name')); + if (!ttValidString($cl_address, true)) $err->add($i18n->get('error.field'), $i18n->get('label.client_address')); + if (!ttValidFloat($cl_tax, true)) $err->add($i18n->get('error.field'), $i18n->get('label.tax')); + if (!ttValidStatus($cl_status)) $err->add($i18n->get('error.field'), $i18n->get('label.status')); if ($err->no()) { if ($request->getParameter('btn_save')) { @@ -91,16 +80,14 @@ header('Location: clients.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.client_exists')); + $err->add($i18n->get('error.object_exists')); } if ($request->getParameter('btn_copy')) { if (!ttClientHelper::getClientByName($cl_name)) { - if (ttClientHelper::insert(array( - 'team_id' => $user->team_id, - 'name' => $cl_name, + if (ttClientHelper::insert(array('name' => $cl_name, 'address' => $cl_address, 'tax' => $cl_tax, 'status' => $cl_status, @@ -108,14 +95,15 @@ header('Location: clients.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.client_exists')); + $err->add($i18n->get('error.object_exists')); } } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.edit_client')); +$smarty->assign('show_projects', $show_projects); +$smarty->assign('title', $i18n->get('title.edit_client')); $smarty->assign('content_page_name', 'client_edit.tpl'); $smarty->display('index.tpl'); diff --git a/clients.php b/clients.php index 0a027b8bd..309eb155a 100644 --- a/clients.php +++ b/clients.php @@ -1,43 +1,31 @@ isPluginEnabled('cl')) { +// Access checks. +if (!(ttAccessAllowed('view_own_clients') || ttAccessAllowed('manage_clients'))) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('cl')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. + +if($user->can('manage_clients')) { + $active_clients = ttGroupHelper::getActiveClients(true); + $inactive_clients = ttGroupHelper::getInactiveClients(true); +} else + $active_clients = $user->getAssignedClients(); -$smarty->assign('active_clients', ttTeamHelper::getActiveClients($user->team_id, true)); -$smarty->assign('inactive_clients', ttTeamHelper::getInactiveClients($user->team_id, true)); -$smarty->assign('title', $i18n->getKey('title.clients')); +$smarty->assign('active_clients', $active_clients); +$smarty->assign('inactive_clients', $inactive_clients); +$smarty->assign('title', $i18n->get('title.clients')); $smarty->assign('content_page_name', 'clients.tpl'); $smarty->display('index.tpl'); diff --git a/cron.php b/cron.php index 7be547d61..5adced71e 100644 --- a/cron.php +++ b/cron.php @@ -1,30 +1,6 @@ = next - and status = 1 and report_id is not null and email is not null"; +$sql = "select c.id, c.cron_spec, c.report_id, c.email, c.cc, c.subject, c.comment, c.report_condition from tt_cron c". + " inner join tt_fav_reports fr on". + " (c.report_id = fr.id and c.group_id = fr.group_id and c.org_id = fr.org_id)". // Report for a correct group. + " inner join tt_users u on (u.id = fr.user_id and u.status = 1)". // Report for an active user. + " where $now >= c.next and fr.status = 1". // Due now. + " and c.status = 1 and c.report_id is not null and c.email is not null"; $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) exit(); @@ -57,23 +37,48 @@ // We have jobs to execute in user language. // Get favorite report details. - $report = ttFavReportHelper::getReport($val['report_id']); - if (!$report) continue; + $options = ttFavReportHelper::getReportOptions($val['report_id']); + if (!$options) continue; // Skip not found report. - // Recycle global $user and $i18n objects, as user settings and language are specific for each report. - $user = new ttUser(null, $report['user_id']); + // Recycle global $user object, as user settings are specific for each report. + $user = new ttUser(null, $options['user_id']); + if (!$user->id) continue; // Skip not found user. + + // Avoid complications with impersonated users, possibly from subgroups. + // Note: this may happen when cron.php is called by a browser who already impersonates. + // This is not supposed to happen in automatic cron job. + if ($user->behalf_id) + continue; // Skip processing on behalf situations entirely. + + // TODO: write a new function ttFavReportHelper::adjustOptions that will use + // a $user object recycled above. Put user handling below into it. + // Also adjust remaining options for potentially changed user access rights and group properties. + // For example, tracking mode may have changed, but fav report options are still old... + // This needs to be fixed. + $options = ttFavReportHelper::adjustOptions($options); + + // Skip users with disabled Notifications plugin. + if (!$user->isPluginEnabled('no')) continue; + + // Recycle $i18n object because language is user-specific. $i18n->load($user->lang); - // Email report. - if (ttReportHelper::sendFavReport($report, $val['email'])) - echo "Report ".$val['report_id']. " sent to ".$val['email']."
"; - else - echo "Error while emailing report...
"; + // Check condition on a report. + $condition_ok = true; + if ($val['report_condition']) + $condition_ok = ttReportHelper::checkFavReportCondition($options, $val['report_condition']); + + // Email report if condition is okay. + if ($condition_ok) { + if (ttReportHelper::sendFavReport($options, $val['subject'], $val['comment'], $val['email'], $val['cc'])) + echo "Report ".$val['report_id']. " sent.
"; + else + echo "Error while emailing report...
"; + } // Calculate next execution time. $next = tdCron::getNextOccurrence($val['cron_spec'], $now + 60); // +60 sec is here to get us correct $next when $now is close to existing "next". // This is because the accuracy of tdcron class appears to be 1 minute. - // Update last and next values in tt_cron. $sql = "update tt_cron set last = $now, next = $next where id = ".$val['id']; $affected = $mdb2->exec($sql); diff --git a/custom_css.php b/custom_css.php new file mode 100644 index 000000000..9aa287321 --- /dev/null +++ b/custom_css.php @@ -0,0 +1,10 @@ +getCustomCss(); diff --git a/dbinstall.php b/dbinstall.php old mode 100755 new mode 100644 index ac88bc422..44fede0cd --- a/dbinstall.php +++ b/dbinstall.php @@ -1,50 +1,60 @@ ".$sql.""; $mdb2 = getConnection(); $affected = $mdb2->exec($sql); if (is_a($affected, 'PEAR_Error')) - print "error: ".$affected->getMessage()."
"; + print 'Error: '.$affected->getMessage().'.
'; else - print "successful update
\n"; + print "Successful update.
\n"; +} + +// ttGenerateKeys - generates keys for groups that do not have them. +function ttGenerateKeys() { + $mdb2 = getConnection(); + $sql = "select id from tt_groups where group_key is null and status = 1"; + $res = $mdb2->query($sql); + if (is_a($res, 'PEAR_Error')) die($res->getMessage()); + + $numGroups = 0; + while ($val = $res->fetchRow()) { + $group_id = $val['id']; + $group_key = $mdb2->quote(ttRandomString()); + $sql = "update tt_groups set group_key = $group_key where id = $group_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) die($affected->getMessage()); + $numGroups++; + } + print "
Generated keys for $numGroups groups.
\n"; } if ($request->isGet()) { echo('

Environment Checks

'); + // Determine if cookies are enabled in browser. + // session_start(); // already called in initialize.php. + $session_id1 = session_id(); + session_destroy(); + session_start(); + $session_id2 = session_id(); + if ($session_id1 != $session_id2) { + echo('Error: browser cookies are off.
'); + } + // Check if WEB-INF/templates_c dir is writable. if (is_writable(APP_DIR.'/WEB-INF/templates_c/')) { echo('WEB-INF/templates_c/ directory is writable.
'); @@ -57,12 +67,15 @@ function setChange($sql) { echo('WEB-INF/config.php file exists.
'); // Config file must start with the PHP opening tag. We are checking this because - // a Unicode editor may insert a byte order mark (BOM) before it. This is not good as it will - // spit white space before output in some situations such as in PDF reports. + // a Unicode editor may insert a byte order mark (BOM) before it. + // This is not good as it is printed as unintentional white space in output. + // Consequences: + // 1) PHP redirects may stop working, depending on server settings. + // 2) PDF reports and attachment downloads will become unusable (cannot open files). $file = fopen(APP_DIR.'/WEB-INF/config.php', 'r'); $line = fgets($file); - if (strcmp('Error: WEB-INF/config.php file does not start with PHP opening tag.
'); + if (strcmp("Error: WEB-INF/config.php file does not start with a PHP opening tag. See explanation.
'); } fclose($file); } else { @@ -78,11 +91,22 @@ function setChange($sql) { } // Check if PHP version is good enough. - $required_version = '5.2.1'; // Something in TCPDF library does not work below this one. - if (version_compare(phpversion(), $required_version, '>=')) { - echo('PHP version: '.phpversion().', good enough.
'); + $required_version = '7.0'; // smarty 4.3.0 that we embed uses null coalescing operator (introduced in php 7). + + // Print a warning about php <= 7.4 because we are no longer testing there. + if (version_compare(phpversion(), '7.4', '<')) { + echo('Warning: This app is no longer tested with PHP version: '.phpversion().'.
'); + } + + // Print a warning about php >= 8.2 because of insufficient testing there. + if (version_compare(phpversion(), '8.2', '>=')) { + echo('Error: This app was not tested with PHP version: '.phpversion().'.
'); } else { - echo('Error: PHP version is not high enough: '.phpversion().'. Required: '.$required_version.'.
'); + if (version_compare(phpversion(), $required_version, '>=')) { + echo('PHP version: '.phpversion().', good enough.
'); + } else { + echo('Error: PHP version is not high enough: '.phpversion().'. Required: '.$required_version.'.
'); + } } // Depending on DSN, require either mysqli or mysql extensions. @@ -117,13 +141,18 @@ function setChange($sql) { // Check ldap extension. if (AUTH_MODULE == 'ldap') { - if (extension_loaded('ldap_')) { + if (extension_loaded('ldap')) { echo('ldap PHP extension is loaded.
'); } else { echo('Error: ldap PHP extension is not loaded. It is required for LDAP authentication.
'); } } + // Check is libxml is enabled (which is a PHP default). + if (!function_exists('libxml_clear_errors')) { + echo('Error: libxml is not enabled. It is required for import group operation (to parse XML).
'); + } + // Check database access. require_once('MDB2.php'); $conn = MDB2::connect(DSN); @@ -150,6 +179,20 @@ function setChange($sql) { } else { echo('There are no tables in database. Execute step 1 - Create database structure.
'); } + + try { + $sql = "select param_value from tt_site_config where param_name = 'version_db'"; + $res = $conn->query($sql); + if (is_a($res, 'MDB2_Error')) { + echo('Error: database schema version query failed. '.$res->getMessage().'
'); + } else { + $val = $res->fetchRow(); + echo('Database version is: '.$val['param_value'].'.'); + } + } catch (Exception $e) { + echo('Caught exception: '.$e->getMessage().'
'); + } + $conn->disconnect(); } @@ -157,7 +200,7 @@ function setChange($sql) { if ($_POST) { print "Processing...
\n"; - if ($_POST["crstructure"]) { + if (array_key_exists('crstructure', $_POST)) { $sqlQuery = join("\n", file("mysql.sql")); $sqlQuery = str_replace("TYPE=MyISAM","",$sqlQuery); $queries = explode(";",$sqlQuery); @@ -165,42 +208,42 @@ function setChange($sql) { foreach ($queries as $query) { $query = trim($query); if (strlen($query)>0) { - setChange($query); + ttExecute($query); } } } } - if ($_POST["convert5to7"]) { - setChange("alter table `activity_log` CHANGE al_comment al_comment text"); - setChange("CREATE TABLE `sysconfig` (`sysc_id` int(11) unsigned NOT NULL auto_increment,`sysc_name` varchar(32) NOT NULL default '',`sysc_value` varchar(70) default NULL, PRIMARY KEY (`sysc_id`), UNIQUE KEY `sysc_id` (`sysc_id`), UNIQUE KEY `sysc_name` (`sysc_name`))"); - setChange("alter table `companies` add c_locktime int(4) default -1"); - setChange("alter table `activity_log` add al_billable tinyint(4) default 0"); - setChange("alter table `sysconfig` drop INDEX `sysc_name`"); - setChange("alter table `sysconfig` add sysc_id_u int(4)"); - setChange("alter table `report_filter_set` add rfs_billable VARCHAR(10)"); - setChange("ALTER TABLE clients MODIFY clnt_id int(11) NOT NULL AUTO_INCREMENT"); - setChange("ALTER TABLE `users` ADD `u_show_pie` smallint(2) DEFAULT '1'"); - setChange("alter table `users` ADD `u_pie_mode` smallint(2) DEFAULT '1'"); - setChange("alter table users drop `u_aprojects`"); - } - - if ($_POST["convert7to133"]) { - setChange("ALTER TABLE users ADD COLUMN u_lang VARCHAR(20) DEFAULT NULL"); - setChange("ALTER TABLE users ADD COLUMN u_email VARCHAR(100) DEFAULT NULL"); - setChange("ALTER TABLE `activity_log` drop `al_proof`"); - setChange("ALTER TABLE `activity_log` drop `al_charge`"); - setChange("ALTER TABLE `activities` drop `a_project_id`"); - setChange("DROP TABLE `activity_status_list`"); - setChange("DROP TABLE `project_status_list`"); - setChange("DROP TABLE `user_status_list`"); - setChange("DROP TABLE `companies_c_id_seq`"); - setChange("ALTER TABLE projects ADD COLUMN p_activities TEXT"); + if (array_key_exists('convert5to7', $_POST)) { + ttExecute("alter table `activity_log` CHANGE al_comment al_comment text"); + ttExecute("CREATE TABLE `sysconfig` (`sysc_id` int(11) unsigned NOT NULL auto_increment,`sysc_name` varchar(32) NOT NULL default '',`sysc_value` varchar(70) default NULL, PRIMARY KEY (`sysc_id`), UNIQUE KEY `sysc_id` (`sysc_id`), UNIQUE KEY `sysc_name` (`sysc_name`))"); + ttExecute("alter table `companies` add c_locktime int(4) default -1"); + ttExecute("alter table `activity_log` add al_billable tinyint(4) default 0"); + ttExecute("alter table `sysconfig` drop INDEX `sysc_name`"); + ttExecute("alter table `sysconfig` add sysc_id_u int(4)"); + ttExecute("alter table `report_filter_set` add rfs_billable VARCHAR(10)"); + ttExecute("ALTER TABLE clients MODIFY clnt_id int(11) NOT NULL AUTO_INCREMENT"); + ttExecute("ALTER TABLE `users` ADD `u_show_pie` smallint(2) DEFAULT '1'"); + ttExecute("alter table `users` ADD `u_pie_mode` smallint(2) DEFAULT '1'"); + ttExecute("alter table users drop `u_aprojects`"); + } + + if (array_key_exists('convert7to133', $_POST)) { + ttExecute("ALTER TABLE users ADD COLUMN u_lang VARCHAR(20) DEFAULT NULL"); + ttExecute("ALTER TABLE users ADD COLUMN u_email VARCHAR(100) DEFAULT NULL"); + ttExecute("ALTER TABLE `activity_log` drop `al_proof`"); + ttExecute("ALTER TABLE `activity_log` drop `al_charge`"); + ttExecute("ALTER TABLE `activities` drop `a_project_id`"); + ttExecute("DROP TABLE `activity_status_list`"); + ttExecute("DROP TABLE `project_status_list`"); + ttExecute("DROP TABLE `user_status_list`"); + ttExecute("DROP TABLE `companies_c_id_seq`"); + ttExecute("ALTER TABLE projects ADD COLUMN p_activities TEXT"); } // The update_projects function updates p_activities field in the projects table so that we could // improve performance of the application by using this field instead of activity_bind table. - if ($_POST["update_projects"]) { + if (array_key_exists('update_projects', $_POST)) { $mdb2 = getConnection(); // $sql = "select p_id from projects where p_status = 1 and p_activities is NULL"; $sql = "select p_id from projects where p_status = 1"; @@ -237,15 +280,15 @@ function setChange($sql) { } } - if ($_POST["convert133to1340"]) { - setChange("ALTER TABLE companies ADD COLUMN c_show_pie smallint(2) DEFAULT 1"); - setChange("ALTER TABLE companies ADD COLUMN c_pie_mode smallint(2) DEFAULT 1"); - setChange("ALTER TABLE companies ADD COLUMN c_lang varchar(20) default NULL"); + if (array_key_exists('convert133to1340', $_POST)) { + ttExecute("ALTER TABLE companies ADD COLUMN c_show_pie smallint(2) DEFAULT 1"); + ttExecute("ALTER TABLE companies ADD COLUMN c_pie_mode smallint(2) DEFAULT 1"); + ttExecute("ALTER TABLE companies ADD COLUMN c_lang varchar(20) default NULL"); } // The update_companies function sets up c_show_pie, c_pie_mode, and c_lang - // fields in the companies table from the corresponding manager fields. - if ($_POST["update_companies"]) { + // fields in the companies table from the corresponding manager fields. + if (array_key_exists('update_companies', $_POST)) { $mdb2 = getConnection(); // Get all active managers. $sql = "select u_company_id, u_show_pie, u_pie_mode, u_lang from users @@ -272,112 +315,112 @@ function setChange($sql) { } } - if ($_POST["convert1340to1485"]) { - setChange("ALTER TABLE users DROP u_show_pie"); - setChange("ALTER TABLE users DROP u_pie_mode"); - setChange("ALTER TABLE users DROP u_lang"); - setChange("ALTER TABLE `users` modify u_login varchar(100) NOT NULL"); - setChange("ALTER TABLE `users` modify u_active smallint(6) default '1'"); - setChange("drop index u_login_idx on users"); - setChange("create unique index u_login_idx on users(u_login, u_active)"); - setChange("ALTER TABLE companies MODIFY `c_lang` varchar(20) NOT NULL default 'en'"); - setChange("ALTER TABLE companies ADD COLUMN `c_date_format` varchar(20) NOT NULL default '%Y-%m-%d'"); - setChange("ALTER TABLE companies ADD COLUMN `c_time_format` varchar(20) NOT NULL default '%H:%M'"); - setChange("ALTER TABLE companies ADD COLUMN `c_week_start` smallint(2) NOT NULL DEFAULT '0'"); - setChange("ALTER TABLE clients MODIFY `clnt_status` smallint(6) default '1'"); - setChange("create unique index clnt_name_idx on clients(clnt_id_um, clnt_name, clnt_status)"); - setChange("ALTER TABLE projects modify p_status smallint(6) default '1'"); - setChange("update projects set p_status = NULL where p_status = 1000"); - setChange("drop index p_manager_idx on projects"); - setChange("create unique index p_name_idx on projects(p_manager_id, p_name, p_status)"); - setChange("ALTER TABLE activities modify a_status smallint(6) default '1'"); - setChange("update activities set a_status = NULL where a_status = 1000"); - setChange("drop index a_manager_idx on activities"); - setChange("create unique index a_name_idx on activities(a_manager_id, a_name, a_status)"); - setChange("RENAME TABLE companies TO teams"); - setChange("RENAME TABLE teams TO att_teams"); - setChange("ALTER TABLE att_teams CHANGE c_id id int(11) NOT NULL auto_increment"); - setChange("RENAME TABLE users TO att_users"); - setChange("update att_users set u_company_id = 0 where u_company_id is NULL"); - setChange("ALTER TABLE att_users CHANGE u_company_id team_id int(11) NOT NULL"); - setChange("RENAME TABLE att_teams TO tt_teams"); - setChange("RENAME TABLE att_users TO tt_users"); - setChange("ALTER TABLE tt_teams CHANGE c_name name varchar(80) NOT NULL"); - setChange("ALTER TABLE `tt_teams` drop `c_www`"); - setChange("ALTER TABLE `tt_teams` MODIFY `name` varchar(80) default NULL"); - setChange("ALTER TABLE clients ADD COLUMN `your_name` varchar(255) default NULL"); - setChange("ALTER TABLE tt_teams ADD COLUMN `address` varchar(255) default NULL"); - setChange("ALTER TABLE invoice_header ADD COLUMN `client_name` varchar(255) default NULL"); - setChange("ALTER TABLE invoice_header ADD COLUMN `client_addr` varchar(255) default NULL"); - setChange("ALTER TABLE report_filter_set ADD COLUMN `rfs_cb_cost` tinyint(4) default '0'"); - setChange("ALTER TABLE activity_log DROP primary key"); - setChange("ALTER TABLE activity_log ADD COLUMN `id` bigint NOT NULL auto_increment primary key"); - setChange("CREATE TABLE `tt_custom_fields` (`id` int(11) NOT NULL auto_increment, `team_id` int(11) NOT NULL, `type` tinyint(4) NOT NULL default '0', `label` varchar(32) NOT NULL default '', PRIMARY KEY (`id`))"); - setChange("CREATE TABLE `tt_custom_field_options` (`id` int(11) NOT NULL auto_increment, `field_id` int(11) NOT NULL, `value` varchar(32) NOT NULL default '', PRIMARY KEY (`id`))"); - setChange("CREATE TABLE `tt_custom_field_log` (`id` bigint NOT NULL auto_increment, `al_id` bigint NOT NULL, `field_id` int(11) NOT NULL, `value` varchar(255) default NULL, PRIMARY KEY (`id`))"); - setChange("ALTER TABLE tt_users DROP u_level"); - setChange("ALTER TABLE tt_custom_fields ADD COLUMN `status` tinyint(4) default '1'"); - setChange("ALTER TABLE report_filter_set ADD COLUMN `rfs_cb_cf_1` tinyint(4) default '0'"); - setChange("ALTER TABLE tt_teams ADD COLUMN `plugins` varchar(255) default NULL"); - setChange("ALTER TABLE tt_teams MODIFY c_locktime int(4) default '0'"); - setChange("ALTER TABLE clients DROP your_name"); - setChange("ALTER TABLE clients DROP clnt_addr_your"); - setChange("ALTER TABLE `tt_custom_fields` ADD COLUMN `required` tinyint(4) default '0'"); - setChange("ALTER TABLE tt_teams DROP c_pie_mode"); - setChange("RENAME TABLE report_filter_set TO tt_fav_reports"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_id id int(11) unsigned NOT NULL auto_increment"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_name name varchar(200) NOT NULL"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_id_u user_id int(11) NOT NULL"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_id_p project_id int(11) default NULL"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_id_a task_id int(11) default NULL"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_users users text default NULL"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_period period tinyint(4) default NULL"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_period_start period_start date default NULL"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_period_finish period_end date default NULL"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_project show_project tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_activity show_task tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_note show_note tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_start show_start tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_finish show_end tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_duration show_duration tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_cost show_cost tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_cf_1 show_custom_field_1 tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_idle show_empty_days tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_cb_totals_only show_totals_only tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_groupby group_by varchar(20) default NULL"); - setChange("ALTER TABLE tt_fav_reports CHANGE rfs_billable billable tinyint(4) default NULL"); - setChange("ALTER TABLE projects CHANGE p_activities tasks text default NULL"); - setChange("ALTER TABLE tt_teams CHANGE c_currency currency varchar(7) default NULL"); - setChange("ALTER TABLE tt_teams CHANGE c_locktime locktime int(4) default '0'"); - setChange("ALTER TABLE tt_teams CHANGE c_show_pie show_pie smallint(2) DEFAULT '1'"); - setChange("ALTER TABLE tt_teams CHANGE c_lang lang varchar(10) NOT NULL default 'en'"); - setChange("ALTER TABLE tt_teams CHANGE c_date_format date_format varchar(20) NOT NULL default '%Y-%m-%d'"); - setChange("ALTER TABLE tt_teams CHANGE c_time_format time_format varchar(20) NOT NULL default '%H:%M'"); - setChange("ALTER TABLE tt_teams CHANGE c_week_start week_start smallint(2) NOT NULL DEFAULT '0'"); - setChange("ALTER TABLE tt_users CHANGE u_id id int(11) NOT NULL auto_increment"); - setChange("ALTER TABLE tt_users CHANGE u_timestamp timestamp timestamp NOT NULL"); - setChange("ALTER TABLE tt_users CHANGE u_login login varchar(50) NOT NULL"); - setChange("drop index u_login_idx on tt_users"); - setChange("create unique index login_idx on tt_users(login, u_active)"); - setChange("ALTER TABLE tt_users CHANGE u_password password varchar(50) default NULL"); - setChange("ALTER TABLE tt_users CHANGE u_name name varchar(100) default NULL"); - setChange("ALTER TABLE tt_users CHANGE u_email email varchar(100) default NULL"); - setChange("ALTER TABLE tt_users CHANGE u_rate rate float(6,2) NOT NULL default '0.00'"); - setChange("update tt_users set u_active = NULL where u_active = 1000"); - setChange("ALTER TABLE tt_users CHANGE u_active status tinyint(4) default '1'"); - setChange("ALTER TABLE tt_teams ADD COLUMN status tinyint(4) default '1'"); - setChange("ALTER TABLE tt_users ADD COLUMN role int(11) default '4'"); - setChange("update tt_users set role = 1024 where login = 'admin'"); - setChange("update tt_users set role = 68 where u_comanager = 1"); - setChange("update tt_users set role = 324 where u_manager_id is null and login != 'admin'"); - setChange("ALTER TABLE user_bind CHANGE ub_checked status tinyint(4) default '1'"); - setChange("ALTER TABLE activities ADD COLUMN team_id int(11) NOT NULL"); - setChange("ALTER TABLE clients ADD COLUMN team_id int(11) NOT NULL"); - setChange("ALTER TABLE projects ADD COLUMN team_id int(11) NOT NULL"); + if (array_key_exists('convert1340to1485', $_POST)) { + ttExecute("ALTER TABLE users DROP u_show_pie"); + ttExecute("ALTER TABLE users DROP u_pie_mode"); + ttExecute("ALTER TABLE users DROP u_lang"); + ttExecute("ALTER TABLE `users` modify u_login varchar(100) NOT NULL"); + ttExecute("ALTER TABLE `users` modify u_active smallint(6) default '1'"); + ttExecute("drop index u_login_idx on users"); + ttExecute("create unique index u_login_idx on users(u_login, u_active)"); + ttExecute("ALTER TABLE companies MODIFY `c_lang` varchar(20) NOT NULL default 'en'"); + ttExecute("ALTER TABLE companies ADD COLUMN `c_date_format` varchar(20) NOT NULL default '%Y-%m-%d'"); + ttExecute("ALTER TABLE companies ADD COLUMN `c_time_format` varchar(20) NOT NULL default '%H:%M'"); + ttExecute("ALTER TABLE companies ADD COLUMN `c_week_start` smallint(2) NOT NULL DEFAULT '0'"); + ttExecute("ALTER TABLE clients MODIFY `clnt_status` smallint(6) default '1'"); + ttExecute("create unique index clnt_name_idx on clients(clnt_id_um, clnt_name, clnt_status)"); + ttExecute("ALTER TABLE projects modify p_status smallint(6) default '1'"); + ttExecute("update projects set p_status = NULL where p_status = 1000"); + ttExecute("drop index p_manager_idx on projects"); + ttExecute("create unique index p_name_idx on projects(p_manager_id, p_name, p_status)"); + ttExecute("ALTER TABLE activities modify a_status smallint(6) default '1'"); + ttExecute("update activities set a_status = NULL where a_status = 1000"); + ttExecute("drop index a_manager_idx on activities"); + ttExecute("create unique index a_name_idx on activities(a_manager_id, a_name, a_status)"); + ttExecute("RENAME TABLE companies TO teams"); + ttExecute("RENAME TABLE teams TO att_teams"); + ttExecute("ALTER TABLE att_teams CHANGE c_id id int(11) NOT NULL auto_increment"); + ttExecute("RENAME TABLE users TO att_users"); + ttExecute("update att_users set u_company_id = 0 where u_company_id is NULL"); + ttExecute("ALTER TABLE att_users CHANGE u_company_id team_id int(11) NOT NULL"); + ttExecute("RENAME TABLE att_teams TO tt_teams"); + ttExecute("RENAME TABLE att_users TO tt_users"); + ttExecute("ALTER TABLE tt_teams CHANGE c_name name varchar(80) NOT NULL"); + ttExecute("ALTER TABLE `tt_teams` drop `c_www`"); + ttExecute("ALTER TABLE `tt_teams` MODIFY `name` varchar(80) default NULL"); + ttExecute("ALTER TABLE clients ADD COLUMN `your_name` varchar(255) default NULL"); + ttExecute("ALTER TABLE tt_teams ADD COLUMN `address` varchar(255) default NULL"); + ttExecute("ALTER TABLE invoice_header ADD COLUMN `client_name` varchar(255) default NULL"); + ttExecute("ALTER TABLE invoice_header ADD COLUMN `client_addr` varchar(255) default NULL"); + ttExecute("ALTER TABLE report_filter_set ADD COLUMN `rfs_cb_cost` tinyint(4) default '0'"); + ttExecute("ALTER TABLE activity_log DROP primary key"); + ttExecute("ALTER TABLE activity_log ADD COLUMN `id` bigint NOT NULL auto_increment primary key"); + ttExecute("CREATE TABLE `tt_custom_fields` (`id` int(11) NOT NULL auto_increment, `team_id` int(11) NOT NULL, `type` tinyint(4) NOT NULL default '0', `label` varchar(32) NOT NULL default '', PRIMARY KEY (`id`))"); + ttExecute("CREATE TABLE `tt_custom_field_options` (`id` int(11) NOT NULL auto_increment, `field_id` int(11) NOT NULL, `value` varchar(32) NOT NULL default '', PRIMARY KEY (`id`))"); + ttExecute("CREATE TABLE `tt_custom_field_log` (`id` bigint NOT NULL auto_increment, `al_id` bigint NOT NULL, `field_id` int(11) NOT NULL, `value` varchar(255) default NULL, PRIMARY KEY (`id`))"); + ttExecute("ALTER TABLE tt_users DROP u_level"); + ttExecute("ALTER TABLE tt_custom_fields ADD COLUMN `status` tinyint(4) default '1'"); + ttExecute("ALTER TABLE report_filter_set ADD COLUMN `rfs_cb_cf_1` tinyint(4) default '0'"); + ttExecute("ALTER TABLE tt_teams ADD COLUMN `plugins` varchar(255) default NULL"); + ttExecute("ALTER TABLE tt_teams MODIFY c_locktime int(4) default '0'"); + ttExecute("ALTER TABLE clients DROP your_name"); + ttExecute("ALTER TABLE clients DROP clnt_addr_your"); + ttExecute("ALTER TABLE `tt_custom_fields` ADD COLUMN `required` tinyint(4) default '0'"); + ttExecute("ALTER TABLE tt_teams DROP c_pie_mode"); + ttExecute("RENAME TABLE report_filter_set TO tt_fav_reports"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_id id int(11) unsigned NOT NULL auto_increment"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_name name varchar(200) NOT NULL"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_id_u user_id int(11) NOT NULL"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_id_p project_id int(11) default NULL"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_id_a task_id int(11) default NULL"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_users users text default NULL"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_period period tinyint(4) default NULL"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_period_start period_start date default NULL"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_period_finish period_end date default NULL"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_cb_project show_project tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_cb_activity show_task tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_cb_note show_note tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_cb_start show_start tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_cb_finish show_end tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_cb_duration show_duration tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_cb_cost show_cost tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_cb_cf_1 show_custom_field_1 tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_cb_idle show_empty_days tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_cb_totals_only show_totals_only tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_groupby group_by varchar(20) default NULL"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE rfs_billable billable tinyint(4) default NULL"); + ttExecute("ALTER TABLE projects CHANGE p_activities tasks text default NULL"); + ttExecute("ALTER TABLE tt_teams CHANGE c_currency currency varchar(7) default NULL"); + ttExecute("ALTER TABLE tt_teams CHANGE c_locktime locktime int(4) default '0'"); + ttExecute("ALTER TABLE tt_teams CHANGE c_show_pie show_pie smallint(2) DEFAULT '1'"); + ttExecute("ALTER TABLE tt_teams CHANGE c_lang lang varchar(10) NOT NULL default 'en'"); + ttExecute("ALTER TABLE tt_teams CHANGE c_date_format date_format varchar(20) NOT NULL default '%Y-%m-%d'"); + ttExecute("ALTER TABLE tt_teams CHANGE c_time_format time_format varchar(20) NOT NULL default '%H:%M'"); + ttExecute("ALTER TABLE tt_teams CHANGE c_week_start week_start smallint(2) NOT NULL DEFAULT '0'"); + ttExecute("ALTER TABLE tt_users CHANGE u_id id int(11) NOT NULL auto_increment"); + ttExecute("ALTER TABLE tt_users CHANGE u_timestamp timestamp timestamp NOT NULL"); + ttExecute("ALTER TABLE tt_users CHANGE u_login login varchar(50) NOT NULL"); + ttExecute("drop index u_login_idx on tt_users"); + ttExecute("create unique index login_idx on tt_users(login, u_active)"); + ttExecute("ALTER TABLE tt_users CHANGE u_password password varchar(50) default NULL"); + ttExecute("ALTER TABLE tt_users CHANGE u_name name varchar(100) default NULL"); + ttExecute("ALTER TABLE tt_users CHANGE u_email email varchar(100) default NULL"); + ttExecute("ALTER TABLE tt_users CHANGE u_rate rate float(6,2) NOT NULL default '0.00'"); + ttExecute("update tt_users set u_active = NULL where u_active = 1000"); + ttExecute("ALTER TABLE tt_users CHANGE u_active status tinyint(4) default '1'"); + ttExecute("ALTER TABLE tt_teams ADD COLUMN status tinyint(4) default '1'"); + ttExecute("ALTER TABLE tt_users ADD COLUMN role int(11) default '4'"); + ttExecute("update tt_users set role = 1024 where login = 'admin'"); + ttExecute("update tt_users set role = 68 where u_comanager = 1"); + ttExecute("update tt_users set role = 324 where u_manager_id is null and login != 'admin'"); + ttExecute("ALTER TABLE user_bind CHANGE ub_checked status tinyint(4) default '1'"); + ttExecute("ALTER TABLE activities ADD COLUMN team_id int(11) NOT NULL"); + ttExecute("ALTER TABLE clients ADD COLUMN team_id int(11) NOT NULL"); + ttExecute("ALTER TABLE projects ADD COLUMN team_id int(11) NOT NULL"); } // The update_to_team_id function sets team_id field projects, activities, and clients tables. - if ($_POST["update_to_team_id"]) { + if (array_key_exists('update_to_team_id', $_POST)) { $mdb2 = getConnection(); // Update projects. @@ -474,172 +517,132 @@ function setChange($sql) { print "Updated $clients_updated clients...
\n"; } - if ($_POST["convert1485to1579"]) { - setChange("ALTER TABLE tt_fav_reports MODIFY id int(11) NOT NULL auto_increment"); - setChange("RENAME TABLE clients TO tt_clients"); - setChange("ALTER TABLE tt_clients CHANGE clnt_id id int(11) NOT NULL AUTO_INCREMENT"); - setChange("ALTER TABLE tt_clients CHANGE clnt_status status tinyint(4) default '1'"); - setChange("ALTER TABLE tt_clients DROP clnt_id_um"); - setChange("ALTER TABLE tt_clients CHANGE clnt_name name varchar(80) NOT NULL"); - setChange("drop index clnt_name_idx on tt_clients"); - setChange("drop index client_name_idx on tt_clients"); - setChange("create unique index client_name_idx on tt_clients(team_id, name, status)"); - setChange("ALTER TABLE tt_teams ADD COLUMN `timestamp` timestamp NOT NULL"); - setChange("ALTER TABLE tt_clients CHANGE clnt_addr_cust address varchar(255) default NULL"); - setChange("ALTER TABLE tt_clients DROP clnt_discount"); - setChange("ALTER TABLE tt_clients DROP clnt_comment"); - setChange("ALTER TABLE tt_clients DROP clnt_fsubtotals"); - setChange("ALTER TABLE tt_clients CHANGE clnt_tax tax float(6,2) NOT NULL default '0.00'"); - setChange("ALTER TABLE activity_log ADD COLUMN client_id int(11) default NULL"); - setChange("ALTER TABLE tt_teams DROP show_pie"); - setChange("ALTER TABLE tt_fav_reports CHANGE group_by sort_by varchar(20) default 'date'"); - setChange("RENAME TABLE tmp_refs TO tt_tmp_refs"); - setChange("ALTER TABLE tt_tmp_refs CHANGE tr_created timestamp timestamp NOT NULL"); - setChange("ALTER TABLE tt_tmp_refs CHANGE tr_code ref char(32) NOT NULL default ''"); - setChange("ALTER TABLE tt_tmp_refs CHANGE tr_userid user_id int(11) NOT NULL"); - setChange("RENAME TABLE projects TO tt_projects"); - setChange("ALTER TABLE tt_projects CHANGE p_id id int(11) NOT NULL auto_increment"); - setChange("ALTER TABLE tt_projects DROP p_timestamp"); - setChange("ALTER TABLE tt_projects CHANGE p_name name varchar(80) NOT NULL"); - setChange("ALTER TABLE tt_projects CHANGE p_status status tinyint(4) default '1'"); - setChange("drop index p_name_idx on tt_projects"); - setChange("create unique index project_idx on tt_projects(team_id, name, status)"); - setChange("RENAME TABLE activities TO tt_tasks"); - setChange("ALTER TABLE tt_tasks CHANGE a_id id int(11) NOT NULL auto_increment"); - setChange("ALTER TABLE tt_tasks DROP a_timestamp"); - setChange("ALTER TABLE tt_tasks CHANGE a_name name varchar(80) NOT NULL"); - setChange("ALTER TABLE tt_tasks CHANGE a_status status tinyint(4) default '1'"); - setChange("drop index a_name_idx on tt_tasks"); - setChange("create unique index task_idx on tt_tasks(team_id, name, status)"); - setChange("RENAME TABLE invoice_header TO tt_invoice_headers"); - setChange("ALTER TABLE tt_invoice_headers CHANGE ih_user_id user_id int(11) NOT NULL"); - setChange("ALTER TABLE tt_invoice_headers CHANGE ih_number number varchar(20) default NULL"); - setChange("ALTER TABLE tt_invoice_headers DROP ih_addr_your"); - setChange("ALTER TABLE tt_invoice_headers DROP ih_addr_cust"); - setChange("ALTER TABLE tt_invoice_headers CHANGE ih_comment comment varchar(255) default NULL"); - setChange("ALTER TABLE tt_invoice_headers CHANGE ih_tax tax float(6,2) default '0.00'"); - setChange("ALTER TABLE tt_invoice_headers CHANGE ih_discount discount float(6,2) default '0.00'"); - setChange("ALTER TABLE tt_invoice_headers CHANGE ih_fsubtotals subtotals tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_users DROP u_comanager"); - setChange("ALTER TABLE tt_tasks DROP a_manager_id"); - setChange("ALTER TABLE tt_projects DROP p_manager_id"); - setChange("ALTER TABLE tt_users DROP u_manager_id"); - setChange("ALTER TABLE activity_bind DROP ab_id"); - setChange("RENAME TABLE activity_bind TO tt_project_task_binds"); - setChange("ALTER TABLE tt_project_task_binds CHANGE ab_id_p project_id int(11) NOT NULL"); - setChange("ALTER TABLE tt_project_task_binds CHANGE ab_id_a task_id int(11) NOT NULL"); - setChange("RENAME TABLE user_bind TO tt_user_project_binds"); - setChange("ALTER TABLE tt_user_project_binds CHANGE ub_rate rate float(6,2) NOT NULL default '0.00'"); - setChange("ALTER TABLE tt_user_project_binds CHANGE ub_id_p project_id int(11) NOT NULL"); - setChange("ALTER TABLE tt_user_project_binds CHANGE ub_id_u user_id int(11) NOT NULL"); - setChange("ALTER TABLE tt_user_project_binds CHANGE ub_id id int(11) NOT NULL auto_increment"); - setChange("CREATE TABLE `tt_client_project_binds` (`client_id` int(11) NOT NULL, `project_id` int(11) NOT NULL)"); - setChange("ALTER TABLE tt_user_project_binds MODIFY rate float(6,2) default '0.00'"); - setChange("ALTER TABLE tt_clients MODIFY tax float(6,2) default '0.00'"); - setChange("RENAME TABLE activity_log TO tt_log"); - setChange("ALTER TABLE tt_log CHANGE al_timestamp timestamp timestamp NOT NULL"); - setChange("ALTER TABLE tt_log CHANGE al_user_id user_id int(11) NOT NULL"); - setChange("ALTER TABLE tt_log CHANGE al_date date date NOT NULL"); - setChange("drop index al_date_idx on tt_log"); - setChange("create index date_idx on tt_log(date)"); - setChange("ALTER TABLE tt_log CHANGE al_from start time default NULL"); - setChange("ALTER TABLE tt_log CHANGE al_duration duration time default NULL"); - setChange("ALTER TABLE tt_log CHANGE al_project_id project_id int(11) NOT NULL"); - setChange("ALTER TABLE tt_log MODIFY project_id int(11) default NULL"); - setChange("ALTER TABLE tt_log CHANGE al_activity_id task_id int(11) default NULL"); - setChange("ALTER TABLE tt_log CHANGE al_comment comment text"); - setChange("ALTER TABLE tt_log CHANGE al_billable billable tinyint(4) default '0'"); - setChange("drop index al_user_id_idx on tt_log"); - setChange("drop index al_project_id_idx on tt_log"); - setChange("drop index al_activity_id_idx on tt_log"); - setChange("create index user_idx on tt_log(user_id)"); - setChange("create index project_idx on tt_log(project_id)"); - setChange("create index task_idx on tt_log(task_id)"); - setChange("ALTER TABLE tt_custom_field_log CHANGE al_id log_id bigint NOT NULL"); - setChange("RENAME TABLE sysconfig TO tt_config"); - setChange("ALTER TABLE tt_config DROP sysc_id"); - setChange("ALTER TABLE tt_config CHANGE sysc_id_u user_id int(11) NOT NULL"); - setChange("ALTER TABLE tt_config CHANGE sysc_name param_name varchar(32) NOT NULL"); - setChange("ALTER TABLE tt_config CHANGE sysc_value param_value varchar(80) default NULL"); - setChange("create unique index param_idx on tt_config(user_id, param_name)"); - setChange("ALTER TABLE tt_log ADD COLUMN invoice_id int(11) default NULL"); - setChange("ALTER TABLE tt_projects ADD COLUMN description varchar(255) default NULL"); - setChange("CREATE TABLE `tt_invoices` (`id` int(11) NOT NULL auto_increment, `team_id` int(11) NOT NULL, `number` varchar(20) default NULL, `client_name` varchar(255) default NULL, `client_addr` varchar(255) default NULL, `comment` varchar(255) default NULL, `tax` float(6,2) default '0.00', `discount` float(6,2) default '0.00', PRIMARY KEY (`id`))"); - setChange("ALTER TABLE tt_invoices drop number"); - setChange("ALTER TABLE tt_invoices drop client_name"); - setChange("ALTER TABLE tt_invoices drop client_addr"); - setChange("ALTER TABLE tt_invoices drop comment"); - setChange("ALTER TABLE tt_invoices drop tax"); - setChange("ALTER TABLE tt_invoices ADD COLUMN name varchar(80) NOT NULL"); - setChange("ALTER TABLE tt_invoices ADD COLUMN client_id int(11) NOT NULL"); - setChange("ALTER TABLE tt_invoices ADD COLUMN start_date date NOT NULL"); - setChange("ALTER TABLE tt_invoices ADD COLUMN end_date date NOT NULL"); - setChange("create unique index name_idx on tt_invoices(team_id, name)"); - setChange("drop index ub_id_u on tt_user_project_binds"); - setChange("create unique index bind_idx on tt_user_project_binds(user_id, project_id)"); - setChange("create index client_idx on tt_log(client_id)"); - setChange("create index invoice_idx on tt_log(invoice_id)"); - } - - if ($_POST["convert1579to1600"]) { - setChange("ALTER TABLE tt_invoices ADD COLUMN date date NOT NULL"); - setChange("ALTER TABLE tt_teams ADD COLUMN custom_logo tinyint(4) default '0'"); - setChange("ALTER TABLE tt_tasks ADD COLUMN description varchar(255) default NULL"); - setChange("ALTER TABLE tt_projects MODIFY name varchar(80) COLLATE utf8_bin NOT NULL"); - setChange("ALTER TABLE tt_users MODIFY login varchar(50) COLLATE utf8_bin NOT NULL"); - setChange("ALTER TABLE tt_tasks MODIFY name varchar(80) COLLATE utf8_bin NOT NULL"); - setChange("ALTER TABLE tt_invoices MODIFY name varchar(80) COLLATE utf8_bin NOT NULL"); - setChange("ALTER TABLE tt_clients MODIFY name varchar(80) COLLATE utf8_bin NOT NULL"); - setChange("ALTER TABLE tt_clients ADD COLUMN projects text default NULL"); - setChange("ALTER TABLE tt_custom_field_log ADD COLUMN option_id int(11) default NULL"); - setChange("ALTER TABLE tt_teams ADD COLUMN tracking_mode smallint(2) NOT NULL DEFAULT '2'"); - setChange("ALTER TABLE tt_teams ADD COLUMN record_type smallint(2) NOT NULL DEFAULT '0'"); - setChange("ALTER TABLE tt_invoices DROP start_date"); - setChange("ALTER TABLE tt_invoices DROP end_date"); - } - - if ($_POST["convert1600to1900"]) { - setChange("DROP TABLE IF EXISTS tt_invoice_headers"); - setChange("ALTER TABLE tt_fav_reports ADD COLUMN `client_id` int(11) default NULL"); - setChange("ALTER TABLE tt_fav_reports ADD COLUMN `cf_1_option_id` int(11) default NULL"); - setChange("ALTER TABLE tt_fav_reports ADD COLUMN `show_client` tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports ADD COLUMN `show_invoice` tinyint(4) NOT NULL default '0'"); - setChange("ALTER TABLE tt_fav_reports ADD COLUMN `group_by` varchar(20) default NULL"); - setChange("CREATE TABLE `tt_expense_items` (`id` bigint NOT NULL auto_increment, `date` date NOT NULL, `user_id` int(11) NOT NULL, `client_id` int(11) default NULL, `project_id` int(11) default NULL, `name` varchar(255) NOT NULL, `cost` decimal(10,2) default '0.00', `invoice_id` int(11) default NULL, PRIMARY KEY (`id`))"); - setChange("create index date_idx on tt_expense_items(date)"); - setChange("create index user_idx on tt_expense_items(user_id)"); - setChange("create index client_idx on tt_expense_items(client_id)"); - setChange("create index project_idx on tt_expense_items(project_id)"); - setChange("create index invoice_idx on tt_expense_items(invoice_id)"); - setChange("ALTER TABLE tt_fav_reports DROP sort_by"); - setChange("ALTER TABLE tt_fav_reports DROP show_empty_days"); - setChange("ALTER TABLE tt_invoices DROP discount"); - setChange("ALTER TABLE tt_users ADD COLUMN `client_id` int(11) default NULL"); - setChange("ALTER TABLE tt_teams ADD COLUMN `decimal_mark` char(1) NOT NULL default '.'"); - setChange("ALTER TABLE tt_fav_reports ADD COLUMN `invoice` tinyint(4) default NULL"); - setChange("CREATE TABLE `tt_cron` (`id` int(11) NOT NULL auto_increment, `cron_spec` varchar(255) NOT NULL, `last` int(11) default NULL, `next` int(11) default NULL, `report_id` int(11) default NULL, `email` varchar(100) default NULL, `status` tinyint(4) default '1', PRIMARY KEY (`id`))"); - setChange("ALTER TABLE tt_cron ADD COLUMN `team_id` int(11) NOT NULL"); - setChange("create index client_idx on tt_client_project_binds(client_id)"); - setChange("create index project_idx on tt_client_project_binds(project_id)"); - setChange("ALTER TABLE tt_log ADD COLUMN status tinyint(4) default '1'"); - setChange("ALTER TABLE tt_custom_field_log ADD COLUMN status tinyint(4) default '1'"); - setChange("ALTER TABLE tt_expense_items ADD COLUMN status tinyint(4) default '1'"); - setChange("ALTER TABLE tt_invoices ADD COLUMN status tinyint(4) default '1'"); - setChange("DROP INDEX name_idx on tt_invoices"); - setChange("create unique index name_idx on tt_invoices(team_id, name, status)"); - setChange("ALTER TABLE tt_teams ADD COLUMN lock_spec varchar(255) default NULL"); - setChange("ALTER TABLE tt_teams DROP locktime"); - setChange("CREATE TABLE `tt_monthly_quota` (`team_id` int(11) NOT NULL, `year` smallint(5) UNSIGNED NOT NULL, `month` tinyint(3) UNSIGNED NOT NULL, `quota` smallint(5) UNSIGNED NOT NULL, PRIMARY KEY (`year`,`month`,`team_id`))"); - setChange("ALTER TABLE `tt_monthly_quota` ADD CONSTRAINT `FK_TT_TEAM_CONSTRAING` FOREIGN KEY (`team_id`) REFERENCES `tt_teams` (`id`) ON DELETE CASCADE ON UPDATE CASCADE"); - setChange("ALTER TABLE `tt_teams` ADD `workday_hours` SMALLINT NULL DEFAULT '8' AFTER `lock_spec`"); - setChange("RENAME TABLE tt_monthly_quota TO tt_monthly_quotas"); - setChange("ALTER TABLE tt_expense_items modify `name` text NOT NULL"); - setChange("ALTER TABLE `tt_teams` ADD `uncompleted_indicators` SMALLINT(2) NOT NULL DEFAULT '0' AFTER `record_type`"); - setChange("CREATE TABLE `tt_predefined_expenses` (`id` int(11) NOT NULL auto_increment, `team_id` int(11) NOT NULL, `name` varchar(255) NOT NULL, `cost` decimal(10,2) default '0.00', PRIMARY KEY (`id`))"); + if (array_key_exists('convert1485to1579', $_POST)) { + ttExecute("ALTER TABLE tt_fav_reports MODIFY id int(11) NOT NULL auto_increment"); + ttExecute("RENAME TABLE clients TO tt_clients"); + ttExecute("ALTER TABLE tt_clients CHANGE clnt_id id int(11) NOT NULL AUTO_INCREMENT"); + ttExecute("ALTER TABLE tt_clients CHANGE clnt_status status tinyint(4) default '1'"); + ttExecute("ALTER TABLE tt_clients DROP clnt_id_um"); + ttExecute("ALTER TABLE tt_clients CHANGE clnt_name name varchar(80) NOT NULL"); + ttExecute("drop index clnt_name_idx on tt_clients"); + ttExecute("drop index client_name_idx on tt_clients"); + ttExecute("create unique index client_name_idx on tt_clients(team_id, name, status)"); + ttExecute("ALTER TABLE tt_teams ADD COLUMN `timestamp` timestamp NOT NULL"); + ttExecute("ALTER TABLE tt_clients CHANGE clnt_addr_cust address varchar(255) default NULL"); + ttExecute("ALTER TABLE tt_clients DROP clnt_discount"); + ttExecute("ALTER TABLE tt_clients DROP clnt_comment"); + ttExecute("ALTER TABLE tt_clients DROP clnt_fsubtotals"); + ttExecute("ALTER TABLE tt_clients CHANGE clnt_tax tax float(6,2) NOT NULL default '0.00'"); + ttExecute("ALTER TABLE activity_log ADD COLUMN client_id int(11) default NULL"); + ttExecute("ALTER TABLE tt_teams DROP show_pie"); + ttExecute("ALTER TABLE tt_fav_reports CHANGE group_by sort_by varchar(20) default 'date'"); + ttExecute("RENAME TABLE tmp_refs TO tt_tmp_refs"); + ttExecute("ALTER TABLE tt_tmp_refs CHANGE tr_created timestamp timestamp NOT NULL"); + ttExecute("ALTER TABLE tt_tmp_refs CHANGE tr_code ref char(32) NOT NULL default ''"); + ttExecute("ALTER TABLE tt_tmp_refs CHANGE tr_userid user_id int(11) NOT NULL"); + ttExecute("RENAME TABLE projects TO tt_projects"); + ttExecute("ALTER TABLE tt_projects CHANGE p_id id int(11) NOT NULL auto_increment"); + ttExecute("ALTER TABLE tt_projects DROP p_timestamp"); + ttExecute("ALTER TABLE tt_projects CHANGE p_name name varchar(80) NOT NULL"); + ttExecute("ALTER TABLE tt_projects CHANGE p_status status tinyint(4) default '1'"); + ttExecute("drop index p_name_idx on tt_projects"); + ttExecute("create unique index project_idx on tt_projects(team_id, name, status)"); + ttExecute("RENAME TABLE activities TO tt_tasks"); + ttExecute("ALTER TABLE tt_tasks CHANGE a_id id int(11) NOT NULL auto_increment"); + ttExecute("ALTER TABLE tt_tasks DROP a_timestamp"); + ttExecute("ALTER TABLE tt_tasks CHANGE a_name name varchar(80) NOT NULL"); + ttExecute("ALTER TABLE tt_tasks CHANGE a_status status tinyint(4) default '1'"); + ttExecute("drop index a_name_idx on tt_tasks"); + ttExecute("create unique index task_idx on tt_tasks(team_id, name, status)"); + ttExecute("RENAME TABLE invoice_header TO tt_invoice_headers"); + ttExecute("ALTER TABLE tt_invoice_headers CHANGE ih_user_id user_id int(11) NOT NULL"); + ttExecute("ALTER TABLE tt_invoice_headers CHANGE ih_number number varchar(20) default NULL"); + ttExecute("ALTER TABLE tt_invoice_headers DROP ih_addr_your"); + ttExecute("ALTER TABLE tt_invoice_headers DROP ih_addr_cust"); + ttExecute("ALTER TABLE tt_invoice_headers CHANGE ih_comment comment varchar(255) default NULL"); + ttExecute("ALTER TABLE tt_invoice_headers CHANGE ih_tax tax float(6,2) default '0.00'"); + ttExecute("ALTER TABLE tt_invoice_headers CHANGE ih_discount discount float(6,2) default '0.00'"); + ttExecute("ALTER TABLE tt_invoice_headers CHANGE ih_fsubtotals subtotals tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_users DROP u_comanager"); + ttExecute("ALTER TABLE tt_tasks DROP a_manager_id"); + ttExecute("ALTER TABLE tt_projects DROP p_manager_id"); + ttExecute("ALTER TABLE tt_users DROP u_manager_id"); + ttExecute("ALTER TABLE activity_bind DROP ab_id"); + ttExecute("RENAME TABLE activity_bind TO tt_project_task_binds"); + ttExecute("ALTER TABLE tt_project_task_binds CHANGE ab_id_p project_id int(11) NOT NULL"); + ttExecute("ALTER TABLE tt_project_task_binds CHANGE ab_id_a task_id int(11) NOT NULL"); + ttExecute("RENAME TABLE user_bind TO tt_user_project_binds"); + ttExecute("ALTER TABLE tt_user_project_binds CHANGE ub_rate rate float(6,2) NOT NULL default '0.00'"); + ttExecute("ALTER TABLE tt_user_project_binds CHANGE ub_id_p project_id int(11) NOT NULL"); + ttExecute("ALTER TABLE tt_user_project_binds CHANGE ub_id_u user_id int(11) NOT NULL"); + ttExecute("ALTER TABLE tt_user_project_binds CHANGE ub_id id int(11) NOT NULL auto_increment"); + ttExecute("CREATE TABLE `tt_client_project_binds` (`client_id` int(11) NOT NULL, `project_id` int(11) NOT NULL)"); + ttExecute("ALTER TABLE tt_user_project_binds MODIFY rate float(6,2) default '0.00'"); + ttExecute("ALTER TABLE tt_clients MODIFY tax float(6,2) default '0.00'"); + ttExecute("RENAME TABLE activity_log TO tt_log"); + ttExecute("ALTER TABLE tt_log CHANGE al_timestamp timestamp timestamp NOT NULL"); + ttExecute("ALTER TABLE tt_log CHANGE al_user_id user_id int(11) NOT NULL"); + ttExecute("ALTER TABLE tt_log CHANGE al_date date date NOT NULL"); + ttExecute("drop index al_date_idx on tt_log"); + ttExecute("create index date_idx on tt_log(date)"); + ttExecute("ALTER TABLE tt_log CHANGE al_from start time default NULL"); + ttExecute("ALTER TABLE tt_log CHANGE al_duration duration time default NULL"); + ttExecute("ALTER TABLE tt_log CHANGE al_project_id project_id int(11) NOT NULL"); + ttExecute("ALTER TABLE tt_log MODIFY project_id int(11) default NULL"); + ttExecute("ALTER TABLE tt_log CHANGE al_activity_id task_id int(11) default NULL"); + ttExecute("ALTER TABLE tt_log CHANGE al_comment comment text"); + ttExecute("ALTER TABLE tt_log CHANGE al_billable billable tinyint(4) default '0'"); + ttExecute("drop index al_user_id_idx on tt_log"); + ttExecute("drop index al_project_id_idx on tt_log"); + ttExecute("drop index al_activity_id_idx on tt_log"); + ttExecute("create index user_idx on tt_log(user_id)"); + ttExecute("create index project_idx on tt_log(project_id)"); + ttExecute("create index task_idx on tt_log(task_id)"); + ttExecute("ALTER TABLE tt_custom_field_log CHANGE al_id log_id bigint NOT NULL"); + ttExecute("RENAME TABLE sysconfig TO tt_config"); + ttExecute("ALTER TABLE tt_config DROP sysc_id"); + ttExecute("ALTER TABLE tt_config CHANGE sysc_id_u user_id int(11) NOT NULL"); + ttExecute("ALTER TABLE tt_config CHANGE sysc_name param_name varchar(32) NOT NULL"); + ttExecute("ALTER TABLE tt_config CHANGE sysc_value param_value varchar(80) default NULL"); + ttExecute("create unique index param_idx on tt_config(user_id, param_name)"); + ttExecute("ALTER TABLE tt_log ADD COLUMN invoice_id int(11) default NULL"); + ttExecute("ALTER TABLE tt_projects ADD COLUMN description varchar(255) default NULL"); + ttExecute("CREATE TABLE `tt_invoices` (`id` int(11) NOT NULL auto_increment, `team_id` int(11) NOT NULL, `number` varchar(20) default NULL, `client_name` varchar(255) default NULL, `client_addr` varchar(255) default NULL, `comment` varchar(255) default NULL, `tax` float(6,2) default '0.00', `discount` float(6,2) default '0.00', PRIMARY KEY (`id`))"); + ttExecute("ALTER TABLE tt_invoices drop number"); + ttExecute("ALTER TABLE tt_invoices drop client_name"); + ttExecute("ALTER TABLE tt_invoices drop client_addr"); + ttExecute("ALTER TABLE tt_invoices drop comment"); + ttExecute("ALTER TABLE tt_invoices drop tax"); + ttExecute("ALTER TABLE tt_invoices ADD COLUMN name varchar(80) NOT NULL"); + ttExecute("ALTER TABLE tt_invoices ADD COLUMN client_id int(11) NOT NULL"); + ttExecute("ALTER TABLE tt_invoices ADD COLUMN start_date date NOT NULL"); + ttExecute("ALTER TABLE tt_invoices ADD COLUMN end_date date NOT NULL"); + ttExecute("create unique index name_idx on tt_invoices(team_id, name)"); + ttExecute("drop index ub_id_u on tt_user_project_binds"); + ttExecute("create unique index bind_idx on tt_user_project_binds(user_id, project_id)"); + ttExecute("create index client_idx on tt_log(client_id)"); + ttExecute("create index invoice_idx on tt_log(invoice_id)"); } - + + if (array_key_exists('convert1579to1600', $_POST)) { + ttExecute("ALTER TABLE tt_invoices ADD COLUMN date date NOT NULL"); + ttExecute("ALTER TABLE tt_teams ADD COLUMN custom_logo tinyint(4) default '0'"); + ttExecute("ALTER TABLE tt_tasks ADD COLUMN description varchar(255) default NULL"); + ttExecute("ALTER TABLE tt_projects MODIFY name varchar(80) COLLATE utf8_bin NOT NULL"); + ttExecute("ALTER TABLE tt_users MODIFY login varchar(50) COLLATE utf8_bin NOT NULL"); + ttExecute("ALTER TABLE tt_tasks MODIFY name varchar(80) COLLATE utf8_bin NOT NULL"); + ttExecute("ALTER TABLE tt_invoices MODIFY name varchar(80) COLLATE utf8_bin NOT NULL"); + ttExecute("ALTER TABLE tt_clients MODIFY name varchar(80) COLLATE utf8_bin NOT NULL"); + ttExecute("ALTER TABLE tt_clients ADD COLUMN projects text default NULL"); + ttExecute("ALTER TABLE tt_custom_field_log ADD COLUMN option_id int(11) default NULL"); + ttExecute("ALTER TABLE tt_teams ADD COLUMN tracking_mode smallint(2) NOT NULL DEFAULT '2'"); + ttExecute("ALTER TABLE tt_teams ADD COLUMN record_type smallint(2) NOT NULL DEFAULT '0'"); + ttExecute("ALTER TABLE tt_invoices DROP start_date"); + ttExecute("ALTER TABLE tt_invoices DROP end_date"); + } + // The update_clients function updates projects field in tt_clients table. - if ($_POST["update_clients"]) { + if (array_key_exists('update_clients', $_POST)) { $mdb2 = getConnection(); $sql = "select id from tt_clients where status = 1 or status = 0"; $res = $mdb2->query($sql); @@ -677,10 +680,10 @@ function setChange($sql) { print "Updated $clients_updated clients...
\n"; } - // The update_custom_fields function updates option_id field field in tt_custom_field_log table. - if ($_POST['update_custom_fields']) { + // The update_custom_fields function updates option_id field in tt_custom_field_log table. + if (array_key_exists('update_custom_fields', $_POST)) { $mdb2 = getConnection(); - $sql = "update tt_custom_field_log set option_id = value where field_id in (select id from tt_custom_fields where type = 2)"; + $sql = "update tt_custom_field_log set option_id = value where option_id is null and value is not null and field_id in (select id from tt_custom_fields where type = 2)"; $affected = $mdb2->exec($sql); if (is_a($affected, 'PEAR_Error')) die($affected->getMessage()); @@ -689,7 +692,7 @@ function setChange($sql) { } // The update_tracking_mode function sets the tracking_mode field in tt_teams table to 2 (== MODE_PROJECTS_AND_TASKS). - if ($_POST['update_tracking_mode']) { + if (array_key_exists('update_tracking_mode', $_POST)) { $mdb2 = getConnection(); $sql = "update tt_teams set tracking_mode = 2 where tracking_mode = 0"; $affected = $mdb2->exec($sql); @@ -699,39 +702,553 @@ function setChange($sql) { print "Updated $affected teams...
\n"; } - if ($_POST["cleanup"]) { + if (array_key_exists('convert1600to11400', $_POST)) { + ttExecute("DROP TABLE IF EXISTS tt_invoice_headers"); + ttExecute("ALTER TABLE tt_fav_reports ADD COLUMN `client_id` int(11) default NULL"); + ttExecute("ALTER TABLE tt_fav_reports ADD COLUMN `cf_1_option_id` int(11) default NULL"); + ttExecute("ALTER TABLE tt_fav_reports ADD COLUMN `show_client` tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports ADD COLUMN `show_invoice` tinyint(4) NOT NULL default '0'"); + ttExecute("ALTER TABLE tt_fav_reports ADD COLUMN `group_by` varchar(20) default NULL"); + ttExecute("CREATE TABLE `tt_expense_items` (`id` bigint NOT NULL auto_increment, `date` date NOT NULL, `user_id` int(11) NOT NULL, `client_id` int(11) default NULL, `project_id` int(11) default NULL, `name` varchar(255) NOT NULL, `cost` decimal(10,2) default '0.00', `invoice_id` int(11) default NULL, PRIMARY KEY (`id`))"); + ttExecute("create index date_idx on tt_expense_items(date)"); + ttExecute("create index user_idx on tt_expense_items(user_id)"); + ttExecute("create index client_idx on tt_expense_items(client_id)"); + ttExecute("create index project_idx on tt_expense_items(project_id)"); + ttExecute("create index invoice_idx on tt_expense_items(invoice_id)"); + ttExecute("ALTER TABLE tt_fav_reports DROP sort_by"); + ttExecute("ALTER TABLE tt_fav_reports DROP show_empty_days"); + ttExecute("ALTER TABLE tt_invoices DROP discount"); + ttExecute("ALTER TABLE tt_users ADD COLUMN `client_id` int(11) default NULL"); + ttExecute("ALTER TABLE tt_teams ADD COLUMN `decimal_mark` char(1) NOT NULL default '.'"); + ttExecute("ALTER TABLE tt_fav_reports ADD COLUMN `invoice` tinyint(4) default NULL"); + ttExecute("CREATE TABLE `tt_cron` (`id` int(11) NOT NULL auto_increment, `cron_spec` varchar(255) NOT NULL, `last` int(11) default NULL, `next` int(11) default NULL, `report_id` int(11) default NULL, `email` varchar(100) default NULL, `status` tinyint(4) default '1', PRIMARY KEY (`id`))"); + ttExecute("ALTER TABLE tt_cron ADD COLUMN `team_id` int(11) NOT NULL"); + ttExecute("create index client_idx on tt_client_project_binds(client_id)"); + ttExecute("create index project_idx on tt_client_project_binds(project_id)"); + ttExecute("ALTER TABLE tt_log ADD COLUMN status tinyint(4) default '1'"); + ttExecute("ALTER TABLE tt_custom_field_log ADD COLUMN status tinyint(4) default '1'"); + ttExecute("ALTER TABLE tt_expense_items ADD COLUMN status tinyint(4) default '1'"); + ttExecute("ALTER TABLE tt_invoices ADD COLUMN status tinyint(4) default '1'"); + ttExecute("DROP INDEX name_idx on tt_invoices"); + ttExecute("create unique index name_idx on tt_invoices(team_id, name, status)"); + ttExecute("ALTER TABLE tt_teams ADD COLUMN lock_spec varchar(255) default NULL"); + ttExecute("ALTER TABLE tt_teams DROP locktime"); + ttExecute("CREATE TABLE `tt_monthly_quota` (`team_id` int(11) NOT NULL, `year` smallint(5) UNSIGNED NOT NULL, `month` tinyint(3) UNSIGNED NOT NULL, `quota` smallint(5) UNSIGNED NOT NULL, PRIMARY KEY (`year`,`month`,`team_id`))"); + ttExecute("ALTER TABLE `tt_monthly_quota` ADD CONSTRAINT `FK_TT_TEAM_CONSTRAING` FOREIGN KEY (`team_id`) REFERENCES `tt_teams` (`id`) ON DELETE CASCADE ON UPDATE CASCADE"); + ttExecute("ALTER TABLE `tt_teams` ADD `workday_hours` SMALLINT NULL DEFAULT '8' AFTER `lock_spec`"); + ttExecute("RENAME TABLE tt_monthly_quota TO tt_monthly_quotas"); + ttExecute("ALTER TABLE tt_expense_items modify `name` text NOT NULL"); + ttExecute("ALTER TABLE `tt_teams` ADD `uncompleted_indicators` SMALLINT(2) NOT NULL DEFAULT '0' AFTER `record_type`"); + ttExecute("CREATE TABLE `tt_predefined_expenses` (`id` int(11) NOT NULL auto_increment, `team_id` int(11) NOT NULL, `name` varchar(255) NOT NULL, `cost` decimal(10,2) default '0.00', PRIMARY KEY (`id`))"); + ttExecute("ALTER TABLE `tt_teams` ADD `task_required` smallint(2) NOT NULL DEFAULT '0' AFTER `tracking_mode`"); + ttExecute("ALTER TABLE `tt_teams` ADD `project_required` smallint(2) NOT NULL DEFAULT '0' AFTER `tracking_mode`"); + ttExecute("ALTER TABLE `tt_cron` ADD `report_condition` varchar(255) default NULL AFTER `email`"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `status` tinyint(4) default '1'"); + ttExecute("ALTER TABLE `tt_teams` ADD `bcc_email` varchar(100) default NULL AFTER `uncompleted_indicators`"); + ttExecute("ALTER TABLE `tt_cron` ADD `cc` varchar(100) default NULL AFTER `email`"); + ttExecute("ALTER TABLE `tt_cron` ADD `subject` varchar(100) default NULL AFTER `cc`"); + ttExecute("ALTER TABLE `tt_log` ADD `paid` tinyint(4) NULL default '0' AFTER `billable`"); + } + + if (array_key_exists('convert11400to11744', $_POST)) { + ttExecute("ALTER TABLE `tt_teams` DROP `address`"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `report_spec` text default NULL AFTER `user_id`"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `paid_status` tinyint(4) default NULL AFTER `invoice`"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `show_paid` tinyint(4) NOT NULL DEFAULT '0' AFTER `show_invoice`"); + ttExecute("ALTER TABLE `tt_expense_items` ADD `paid` tinyint(4) NULL default '0' AFTER `invoice_id`"); + ttExecute("ALTER TABLE `tt_monthly_quotas` MODIFY `quota` decimal(5,2) NOT NULL"); + ttExecute("ALTER TABLE `tt_teams` MODIFY `workday_hours` decimal(5,2) DEFAULT '8.00'"); + ttExecute("ALTER TABLE `tt_teams` ADD `config` text default NULL AFTER `custom_logo`"); + ttExecute("ALTER TABLE `tt_monthly_quotas` ADD `minutes` int(11) DEFAULT NULL"); + ttExecute("ALTER TABLE `tt_teams` ADD `workday_minutes` smallint(4) DEFAULT '480' AFTER `workday_hours`"); + ttExecute("UPDATE `tt_teams` SET `workday_minutes` = 60 * `workday_hours`"); + ttExecute("ALTER TABLE `tt_teams` DROP `workday_hours`"); + ttExecute("UPDATE `tt_monthly_quotas` SET `minutes` = 60 * `quota`"); + ttExecute("ALTER TABLE `tt_monthly_quotas` DROP `quota`"); + ttExecute("ALTER TABLE `tt_teams` DROP `uncompleted_indicators`"); + ttExecute("ALTER TABLE `tt_users` MODIFY `timestamp` timestamp default CURRENT_TIMESTAMP"); + ttExecute("ALTER TABLE `tt_teams` MODIFY `timestamp` timestamp default CURRENT_TIMESTAMP"); + ttExecute("ALTER TABLE `tt_log` MODIFY `timestamp` timestamp default CURRENT_TIMESTAMP"); + ttExecute("ALTER TABLE `tt_tmp_refs` MODIFY `timestamp` timestamp default CURRENT_TIMESTAMP"); + ttExecute("CREATE TABLE `tt_roles` (`id` int(11) NOT NULL auto_increment, `team_id` int(11) NOT NULL, `name` varchar(80) default NULL, `rank` int(11) default 0, `rights` text default NULL, `status` tinyint(4) default 1, PRIMARY KEY (`id`))"); + ttExecute("create unique index role_idx on tt_roles(team_id, `rank`, status)"); + ttExecute("ALTER TABLE `tt_roles` ADD `description` varchar(255) default NULL AFTER `name`"); + ttExecute("ALTER TABLE `tt_users` ADD `role_id` int(11) default NULL AFTER `role`"); + ttExecute("CREATE TABLE `tt_site_config` (`param_name` varchar(32) NOT NULL, `param_value` text default NULL, `created` datetime default NULL, `updated` datetime default NULL, PRIMARY KEY (`param_name`))"); + ttExecute("INSERT INTO `tt_site_config` (`param_name`, `param_value`, `created`) VALUES ('version_db', '1.17.34', now())"); + ttExecute("INSERT INTO `tt_roles` (`team_id`, `name`, `rank`, `rights`) VALUES (0, 'Site administrator', 1024, 'administer_site')"); + ttExecute("INSERT INTO `tt_roles` (`team_id`, `name`, `rank`, `rights`) VALUES (0, 'Top manager', 512, 'data_entry,view_own_data,manage_own_settings,view_users,on_behalf_data_entry,view_data,override_punch_mode,swap_roles,approve_timesheets,manage_users,manage_projects,manage_tasks,manage_custom_fields,manage_clients,manage_invoices,manage_features,manage_basic_settings,manage_advanced_settings,manage_roles,export_data,manage_subgroups')"); + ttExecute("UPDATE `tt_site_config` SET `param_value` = '1.17.35' where param_name = 'version_db'"); + ttExecute("update `tt_users` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.35') set role_id = (select id from tt_roles where `rank` = 1024) where role = 1024"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.35') set rights = 'data_entry,view_own_reports,view_own_charts,view_own_invoices,manage_own_settings,view_users,on_behalf_data_entry,view_reports,view_charts,override_punch_mode,swap_roles,approve_timesheets,manage_users,manage_projects,manage_tasks,manage_custom_fields,manage_clients,manage_invoices,manage_features,manage_basic_settings,manage_advanced_settings,manage_roles,export_data,manage_subgroups' where team_id = 0 and `rank` = 512"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.35') set rights = replace(rights, 'view_own_data', 'view_own_reports,view_own_charts') where team_id > 0"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.35') set rights = replace(rights, 'view_data', 'view_reports,view_charts') where team_id > 0"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.35') set rights = replace(rights, 'view_own_charts,manage_own_settings', 'view_own_charts,view_own_invoices,manage_own_settings') where team_id > 0 and `rank` = 16"); + ttExecute("UPDATE `tt_site_config` SET `param_value` = '1.17.40' where param_name = 'version_db'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.40') set rights = replace(rights, 'on_behalf_data_entry', 'track_time,track_expenses')"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.40') set rights = replace(rights, 'data_entry', 'track_own_time,track_own_expenses')"); + ttExecute("UPDATE `tt_site_config` SET `param_value` = '1.17.43' where param_name = 'version_db'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.43') set rights = replace(rights, 'override_punch_mode,swap_roles', 'override_punch_mode,override_date_lock,swap_roles')"); + ttExecute("UPDATE `tt_site_config` SET `param_value` = '1.17.44' where param_name = 'version_db'"); + } + + // The update_role_id function assigns a role_id to users, who don't have it. + if (array_key_exists('update_role_id', $_POST)) { + import('I18n'); $mdb2 = getConnection(); - $inactive_teams = ttTeamHelper::getInactiveTeams(); - $count = count($inactive_teams); - print "$count inactive teams found...
\n"; + $sql = "select u.id, u.team_id, u.role, t.lang from tt_users u inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.44') left join tt_teams t on (u.team_id = t.id) where u.role_id is NULL and u.status is NOT NULL"; + $res = $mdb2->query($sql); + if (is_a($res, 'PEAR_Error')) die($res->getMessage()); + + $users_updated = 0; + // Iterate through users. + while ($val = $res->fetchRow()) { + + $user_id = $val['id']; + $team_id = $val['team_id']; + $lang = $val['lang']; + $legacy_role = $val['role']; + + $sql = "select count(*) as count from tt_roles where team_id = $team_id"; + $result = $mdb2->query($sql); + if (is_a($result, 'PEAR_Error')) die($result->getMessage()); + $row = $result->fetchRow(); + if ($row['count'] == 0) + ttRoleHelper::createPredefinedRoles_1_17_44($team_id, $lang); + + // Obtain new role id based on legacy role. + $role_id = ttRoleHelper::getRoleByRank_1_17_44($legacy_role, $team_id); + if (!$role_id) continue; // Role not found, nothing to do. + + $sql = "update tt_users set role_id = $role_id where id = $user_id and team_id = $team_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) die($affected->getMessage()); + + $users_updated++; + // if ($users_updated >= 1000) break; // TODO: uncomment for large user sets to run multiple times. + } + print "Updated $users_updated users...
\n"; + } + + if (array_key_exists('convert11744to11797', $_POST)) { + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.44') set rights = replace(rights, 'override_punch_mode,override_date_lock', 'override_punch_mode,override_own_punch_mode,override_date_lock')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.48' where param_name = 'version_db' and param_value = '1.17.44'"); + ttExecute("update `tt_users` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.48') set role_id = (select id from tt_roles where team_id = 0 and `rank` = 512) where role = 324"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.49' where param_name = 'version_db' and param_value = '1.17.48'"); + ttExecute("ALTER TABLE `tt_users` drop role"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.50' where param_name = 'version_db' and param_value = '1.17.49'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.50') set rights = replace(rights, 'override_date_lock,swap_roles', 'override_date_lock,override_own_date_lock,swap_roles')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.51' where param_name = 'version_db' and param_value = '1.17.50'"); + ttExecute("ALTER TABLE `tt_users` ADD `created` datetime default NULL AFTER `email`"); + ttExecute("ALTER TABLE `tt_users` ADD `created_ip` varchar(45) default NULL AFTER `created`"); + ttExecute("ALTER TABLE `tt_users` ADD `created_by` int(11) default NULL AFTER `created_ip`"); + ttExecute("ALTER TABLE `tt_users` ADD `modified` datetime default NULL AFTER `created_by`"); + ttExecute("ALTER TABLE `tt_users` ADD `modified_ip` varchar(45) default NULL AFTER `modified`"); + ttExecute("ALTER TABLE `tt_users` ADD `modified_by` int(11) default NULL AFTER `modified_ip`"); + ttExecute("ALTER TABLE `tt_users` ADD `accessed` datetime default NULL AFTER `modified_by`"); + ttExecute("ALTER TABLE `tt_users` ADD `accessed_ip` varchar(45) default NULL AFTER `accessed`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.52' where param_name = 'version_db' and param_value = '1.17.51'"); + ttExecute("ALTER TABLE `tt_site_config` CHANGE `updated` `modified` datetime default NULL"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.53', modified = now() where param_name = 'version_db' and param_value = '1.17.52'"); + ttExecute("ALTER TABLE `tt_log` ADD `created` datetime default NULL AFTER `paid`"); + ttExecute("ALTER TABLE `tt_log` ADD `created_ip` varchar(45) default NULL AFTER `created`"); + ttExecute("ALTER TABLE `tt_log` ADD `created_by` int(11) default NULL AFTER `created_ip`"); + ttExecute("ALTER TABLE `tt_log` ADD `modified` datetime default NULL AFTER `created_by`"); + ttExecute("ALTER TABLE `tt_log` ADD `modified_ip` varchar(45) default NULL AFTER `modified`"); + ttExecute("ALTER TABLE `tt_log` ADD `modified_by` int(11) default NULL AFTER `modified_ip`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.56', modified = now() where param_name = 'version_db' and param_value = '1.17.53'"); + ttExecute("ALTER TABLE `tt_expense_items` ADD `created` datetime default NULL AFTER `paid`"); + ttExecute("ALTER TABLE `tt_expense_items` ADD `created_ip` varchar(45) default NULL AFTER `created`"); + ttExecute("ALTER TABLE `tt_expense_items` ADD `created_by` int(11) default NULL AFTER `created_ip`"); + ttExecute("ALTER TABLE `tt_expense_items` ADD `modified` datetime default NULL AFTER `created_by`"); + ttExecute("ALTER TABLE `tt_expense_items` ADD `modified_ip` varchar(45) default NULL AFTER `modified`"); + ttExecute("ALTER TABLE `tt_expense_items` ADD `modified_by` int(11) default NULL AFTER `modified_ip`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.59', modified = now() where param_name = 'version_db' and param_value = '1.17.56'"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `show_ip` tinyint(4) NOT NULL DEFAULT '0' AFTER `show_paid`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.61', modified = now() where param_name = 'version_db' and param_value = '1.17.59'"); + ttExecute("update `tt_log` l inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.61') set l.created = l.timestamp where l.created is null"); + ttExecute("ALTER TABLE `tt_log` drop `timestamp`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.64', modified = now() where param_name = 'version_db' and param_value = '1.17.61'"); + ttExecute("ALTER TABLE `tt_teams` ADD `created` datetime default NULL AFTER `config`"); + ttExecute("ALTER TABLE `tt_teams` ADD `created_ip` varchar(45) default NULL AFTER `created`"); + ttExecute("ALTER TABLE `tt_teams` ADD `created_by` int(11) default NULL AFTER `created_ip`"); + ttExecute("ALTER TABLE `tt_teams` ADD `modified` datetime default NULL AFTER `created_by`"); + ttExecute("ALTER TABLE `tt_teams` ADD `modified_ip` varchar(45) default NULL AFTER `modified`"); + ttExecute("ALTER TABLE `tt_teams` ADD `modified_by` int(11) default NULL AFTER `modified_ip`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.65', modified = now() where param_name = 'version_db' and param_value = '1.17.64'"); + ttExecute("update `tt_teams` t inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.65') set t.created = t.timestamp where t.created is null"); + ttExecute("update `tt_users` u inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.65') set u.created = u.timestamp where u.created is null"); + ttExecute("ALTER TABLE `tt_teams` drop `timestamp`"); + ttExecute("ALTER TABLE `tt_users` drop `timestamp`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.66', modified = now() where param_name = 'version_db' and param_value = '1.17.65'"); + ttExecute("ALTER TABLE `tt_tmp_refs` ADD `created` datetime default NULL AFTER `timestamp`"); + ttExecute("ALTER TABLE `tt_tmp_refs` drop `timestamp`"); + ttExecute("delete from `tt_tmp_refs`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.67', modified = now() where param_name = 'version_db' and param_value = '1.17.66'"); + ttExecute("ALTER TABLE `tt_teams` ADD `parent_id` int(11) default NULL AFTER `id`"); + ttExecute("ALTER TABLE `tt_teams` ADD `org_id` int(11) default NULL AFTER `parent_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.76', modified = now() where param_name = 'version_db' and param_value = '1.17.67'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.76') set rights = replace(rights, ',manage_users', ',manage_own_account,manage_users')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.77', modified = now() where param_name = 'version_db' and param_value = '1.17.76'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.77') set rights = replace(rights, 'manage_own_settings,view_users', 'manage_own_settings,view_projects,view_users')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.78', modified = now() where param_name = 'version_db' and param_value = '1.17.77'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.78') set rights = replace(rights, 'manage_own_settings,view_projects,view_users', 'view_own_projects,manage_own_settings,view_users')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.79', modified = now() where param_name = 'version_db' and param_value = '1.17.78'"); + ttExecute("RENAME TABLE `tt_teams` TO `tt_groups`"); + ttExecute("ALTER TABLE `tt_monthly_quotas` DROP FOREIGN KEY FK_TT_TEAM_CONSTRAING"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.80', modified = now() where param_name = 'version_db' and param_value = '1.17.79'"); + ttExecute("ALTER TABLE `tt_roles` CHANGE `team_id` `group_id` int(11) NOT NULL"); + ttExecute("ALTER TABLE `tt_users` CHANGE `team_id` `group_id` int(11) NOT NULL"); + ttExecute("ALTER TABLE `tt_projects` CHANGE `team_id` `group_id` int(11) NOT NULL"); + ttExecute("ALTER TABLE `tt_tasks` CHANGE `team_id` `group_id` int(11) NOT NULL"); + ttExecute("ALTER TABLE `tt_invoices` CHANGE `team_id` `group_id` int(11) NOT NULL"); + ttExecute("ALTER TABLE `tt_cron` CHANGE `team_id` `group_id` int(11) NOT NULL"); + ttExecute("ALTER TABLE `tt_clients` CHANGE `team_id` `group_id` int(11) NOT NULL"); + ttExecute("ALTER TABLE `tt_custom_fields` CHANGE `team_id` `group_id` int(11) NOT NULL"); + ttExecute("ALTER TABLE `tt_predefined_expenses` CHANGE `team_id` `group_id` int(11) NOT NULL"); + ttExecute("ALTER TABLE `tt_monthly_quotas` CHANGE `team_id` `group_id` int(11) NOT NULL"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.81', modified = now() where param_name = 'version_db' and param_value = '1.17.80'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.81') set rights = replace(rights, ',manage_invoices', ',manage_invoices,view_all_reports')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.82', modified = now() where param_name = 'version_db' and param_value = '1.17.81'"); + ttExecute("ALTER TABLE `tt_groups` ADD `allow_ip` varchar(255) default NULL AFTER `bcc_email`"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.82') set rights = replace(rights, 'manage_invoices,view_all_reports', 'manage_invoices,override_allow_ip,view_all_reports')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.83', modified = now() where param_name = 'version_db' and param_value = '1.17.82'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.83') set rights = replace(rights, 'view_own_projects,manage_own_settings', 'view_own_projects,view_own_tasks,manage_own_settings')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.84', modified = now() where param_name = 'version_db' and param_value = '1.17.83'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.84') set rights = replace(rights, 'view_charts,override_punch_mode', 'view_charts,view_own_clients,override_punch_mode')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.85', modified = now() where param_name = 'version_db' and param_value = '1.17.84'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.85') set rights = replace(rights, 'override_allow_ip,view_all_reports', 'override_allow_ip,manage_basic_settings,view_all_reports')"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.85') set rights = replace(rights, 'manage_features,manage_basic_setting,manage_advanced_settings', 'manage_features,manage_advanced_settings')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.86', modified = now() where param_name = 'version_db' and param_value = '1.17.85'"); + ttExecute("ALTER TABLE `tt_groups` ADD `password_complexity` varchar(64) default NULL AFTER `allow_ip`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.87', modified = now() where param_name = 'version_db' and param_value = '1.17.86'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.17.87') set rights = replace(rights, 'manage_subgroups', 'manage_subgroups,delete_group') where `rank` = 512"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.88', modified = now() where param_name = 'version_db' and param_value = '1.17.87'"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `show_work_units` tinyint(4) NOT NULL DEFAULT '0' AFTER `show_custom_field_1`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.92', modified = now() where param_name = 'version_db' and param_value = '1.17.88'"); + ttExecute("ALTER TABLE `tt_log` ADD `group_id` int(11) default NULL AFTER `user_id`"); + ttExecute("ALTER TABLE `tt_expense_items` ADD `group_id` int(11) default NULL AFTER `user_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.96', modified = now() where param_name = 'version_db' and param_value = '1.17.92'"); + ttExecute("create index group_idx on tt_log(group_id)"); + ttExecute("create index group_idx on tt_expense_items(group_id)"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.17.97', modified = now() where param_name = 'version_db' and param_value = '1.17.96'"); + } + + // The update_group_id function updates group_id field in tt_log and tt_expense_items tables. + if (array_key_exists('update_group_id', $_POST)) { + $mdb2 = getConnection(); + + $sql = "(select distinct user_id from tt_log where group_id is null) union distinct (select distinct user_id from tt_expense_items where group_id is null)"; + $res = $mdb2->query($sql); + if (is_a($res, 'PEAR_Error')) { + die($res->getMessage()); + } + $users_updated = 0; + $tt_log_records_updated = 0; + $tt_expense_items_updated = 0; + + // Iterate through result set. + while ($val = $res->fetchRow()) { + $user_id = $val['user_id']; + $sql = "select group_id from tt_users where id = $user_id"; + $result = $mdb2->query($sql); + if (is_a($result, 'PEAR_Error')) { + die($res->getMessage()); + } + $value = $result->fetchRow(); + $group_id = $value['group_id']; + + if ($group_id) { + $sql = "update tt_log set group_id = $group_id where user_id = $user_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) { + die($affected->getMessage()); + } + $tt_log_records_updated += $affected; + + $sql = "update tt_expense_items set group_id = $group_id where user_id = $user_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) { + die($affected->getMessage()); + } + $tt_expense_items_updated += $affected; + $users_updated++; + } else { + print "Error: Could not find group for user $user_id...
\n"; + } + } + print "Updated $tt_log_records_updated tt_log records...
\n"; + print "Updated $tt_expense_items_updated tt_expense_items records...
\n"; + } + + if (array_key_exists('convert11797to11900', $_POST)) { + ttExecute("ALTER TABLE `tt_fav_reports` CHANGE `group_by` `group_by1` varchar(20) default NULL"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `group_by2` varchar(20) default NULL AFTER `group_by1`"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `group_by3` varchar(20) default NULL AFTER `group_by2`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.00', modified = now() where param_name = 'version_db' and param_value = '1.17.97'"); + ttExecute("create index log_idx on tt_custom_field_log(log_id)"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.05', modified = now() where param_name = 'version_db' and param_value = '1.18.00'"); + ttExecute("UPDATE `tt_groups` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.05') set org_id = id where org_id is null"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.06', modified = now() where param_name = 'version_db' and param_value = '1.18.05'"); + ttExecute("ALTER TABLE `tt_users` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_users` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.06') set org_id = group_id where org_id is null"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.07', modified = now() where param_name = 'version_db' and param_value = '1.18.06'"); + ttExecute("ALTER TABLE `tt_roles` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.07') set org_id = group_id where org_id is null"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.08', modified = now() where param_name = 'version_db' and param_value = '1.18.07'"); + ttExecute("ALTER TABLE `tt_clients` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.09', modified = now() where param_name = 'version_db' and param_value = '1.18.08'"); + ttExecute("UPDATE `tt_clients` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.09') set org_id = group_id where org_id is null"); + ttExecute("ALTER TABLE `tt_projects` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("ALTER TABLE `tt_tasks` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.10', modified = now() where param_name = 'version_db' and param_value = '1.18.09'"); + ttExecute("UPDATE `tt_projects` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.10') set org_id = group_id where org_id is null"); + ttExecute("UPDATE `tt_tasks` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.10') set org_id = group_id where org_id is null"); + ttExecute("ALTER TABLE `tt_log` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("ALTER TABLE `tt_invoices` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.11', modified = now() where param_name = 'version_db' and param_value = '1.18.10'"); + ttExecute("UPDATE `tt_log` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.11') set org_id = group_id where org_id is null"); + ttExecute("UPDATE `tt_invoices` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.11') set org_id = group_id where org_id is null"); + ttExecute("ALTER TABLE `tt_user_project_binds` ADD `group_id` int(11) default NULL AFTER `project_id`"); + ttExecute("ALTER TABLE `tt_user_project_binds` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("ALTER TABLE `tt_project_task_binds` ADD `group_id` int(11) default NULL AFTER `task_id`"); + ttExecute("ALTER TABLE `tt_project_task_binds` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.12', modified = now() where param_name = 'version_db' and param_value = '1.18.11'"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.13', modified = now() where param_name = 'version_db' and param_value = '1.18.12'"); + ttExecute("ALTER TABLE `tt_users` MODIFY `login` varchar(50) COLLATE utf8mb4_bin NOT NULL"); + ttExecute("ALTER TABLE `tt_projects` MODIFY `name` varchar(80) COLLATE utf8mb4_bin NOT NULL"); + ttExecute("ALTER TABLE `tt_tasks` MODIFY `name` varchar(80) COLLATE utf8mb4_bin NOT NULL"); + ttExecute("ALTER TABLE `tt_invoices` MODIFY `name` varchar(80) COLLATE utf8mb4_bin NOT NULL"); + ttExecute("ALTER TABLE `tt_clients` MODIFY `name` varchar(80) COLLATE utf8mb4_bin NOT NULL"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.14', modified = now() where param_name = 'version_db' and param_value = '1.18.13'"); + ttExecute("ALTER TABLE `tt_monthly_quotas` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_monthly_quotas` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.14') set org_id = group_id where org_id is null"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.15', modified = now() where param_name = 'version_db' and param_value = '1.18.14'"); + ttExecute("ALTER TABLE `tt_predefined_expenses` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.16', modified = now() where param_name = 'version_db' and param_value = '1.18.15'"); + ttExecute("UPDATE `tt_predefined_expenses` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.16') set org_id = group_id where org_id is null"); + ttExecute("ALTER TABLE `tt_expense_items` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.17', modified = now() where param_name = 'version_db' and param_value = '1.18.16'"); + ttExecute("UPDATE `tt_expense_items` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.17') set org_id = group_id where org_id is null"); + ttExecute("update `tt_user_project_binds` upb inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.17') inner join `tt_users` u on u.id = upb.user_id set upb.group_id = u.group_id, upb.org_id = u.org_id where upb.org_id is null"); + ttExecute("update `tt_project_task_binds` ptb inner join tt_site_config sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.17') inner join `tt_projects` p on p.id = ptb.project_id set ptb.group_id = p.group_id, ptb.org_id = p.org_id where ptb.org_id is null"); + ttExecute("create unique index project_task_idx on tt_project_task_binds(project_id, task_id)"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.18', modified = now() where param_name = 'version_db' and param_value = '1.18.17'"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `group_id` int(11) default NULL AFTER `user_id`"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.19', modified = now() where param_name = 'version_db' and param_value = '1.18.18'"); + ttExecute("update `tt_fav_reports` fr inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.19') inner join `tt_users` u on u.id = fr.user_id set fr.group_id = u.group_id, fr.org_id = u.org_id where fr.org_id is null"); + ttExecute("ALTER TABLE `tt_cron` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.20', modified = now() where param_name = 'version_db' and param_value = '1.18.19'"); + ttExecute("UPDATE `tt_cron` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.20') set org_id = group_id where org_id is null"); + ttExecute("ALTER TABLE `tt_client_project_binds` ADD `group_id` int(11) default NULL AFTER `project_id`"); + ttExecute("ALTER TABLE `tt_client_project_binds` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("update `tt_client_project_binds` cpb inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.20') inner join `tt_clients` c on c.id = cpb.client_id set cpb.group_id = c.group_id, cpb.org_id = c.org_id where cpb.org_id is null"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.21', modified = now() where param_name = 'version_db' and param_value = '1.18.20'"); + ttExecute("create unique index client_project_idx on tt_client_project_binds(client_id, project_id)"); + ttExecute("ALTER TABLE `tt_config` ADD `group_id` int(11) default NULL AFTER `user_id`"); + ttExecute("ALTER TABLE `tt_config` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.22', modified = now() where param_name = 'version_db' and param_value = '1.18.21'"); + ttExecute("update `tt_config` c inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.22') inner join `tt_users` u on u.id = c.user_id set c.group_id = u.group_id, c.org_id = u.org_id where c.org_id is null"); + ttExecute("ALTER TABLE `tt_custom_fields` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("ALTER TABLE `tt_custom_field_options` ADD `group_id` int(11) default NULL AFTER `id`"); + ttExecute("ALTER TABLE `tt_custom_field_options` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("ALTER TABLE `tt_custom_field_log` ADD `group_id` int(11) default NULL AFTER `id`"); + ttExecute("ALTER TABLE `tt_custom_field_log` ADD `org_id` int(11) default NULL AFTER `group_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.23', modified = now() where param_name = 'version_db' and param_value = '1.18.22'"); + ttExecute("UPDATE `tt_custom_fields` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.23') set org_id = group_id where org_id is null"); + ttExecute("update `tt_custom_field_options` cfo inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.23') inner join `tt_custom_fields` cf on cf.id = cfo.field_id set cfo.group_id = cf.group_id, cfo.org_id = cf.org_id where cfo.org_id is null"); + ttExecute("update `tt_custom_field_log` cfl inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.23') inner join `tt_custom_fields` cf on cf.id = cfl.field_id set cfl.group_id = cf.group_id, cfl.org_id = cf.org_id where cfl.org_id is null"); + ttExecute("ALTER TABLE `tt_custom_field_options` ADD `status` tinyint(4) default '1' after `value`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.24', modified = now() where param_name = 'version_db' and param_value = '1.18.23'"); + ttExecute("ALTER TABLE `tt_groups` ADD COLUMN `description` varchar(255) default NULL after `name`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.26', modified = now() where param_name = 'version_db' and param_value = '1.18.24'"); + ttExecute("update `tt_client_project_binds` cpb inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.26') inner join `tt_clients` c on c.id = cpb.client_id set cpb.group_id = c.group_id, cpb.org_id = c.org_id where cpb.org_id is null"); + ttExecute("ALTER TABLE `tt_users` ADD COLUMN `quota_percent` float(6,2) default NULL after `rate`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.34', modified = now() where param_name = 'version_db' and param_value = '1.18.26'"); + ttExecute("update `tt_users` u inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.34') set u.quota_percent = 100.00 where u.quota_percent is null"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.36', modified = now() where param_name = 'version_db' and param_value = '1.18.34'"); + ttExecute("update `tt_users` u inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.36') set u.quota_percent = 100.00 where u.quota_percent is null"); + ttExecute("ALTER TABLE `tt_users` modify `quota_percent` float(6,2) NOT NULL default '100.00'"); + ttExecute("CREATE TABLE `tt_timesheets` (`id` int(11) NOT NULL auto_increment, `user_id` int(11) NOT NULL, `group_id` int(11) default NULL, `org_id` int(11) default NULL, `client_id` int(11) default NULL, `name` varchar(80) COLLATE utf8mb4_bin NOT NULL, `submit_status` tinyint(4) default NULL, `submitter_comment` text, `approval_status` tinyint(4) default NULL, `manager_comment` text, `created` datetime default NULL, `created_ip` varchar(45) default NULL, `created_by` int(11) default NULL, `modified` datetime default NULL, `modified_ip` varchar(45) default NULL, `modified_by` int(11) default NULL, `status` tinyint(4) default 1, PRIMARY KEY (`id`))"); + ttExecute("ALTER TABLE `tt_log` ADD `timesheet_id` int(11) default NULL AFTER `task_id`"); + ttExecute("create index timesheet_idx on tt_log(timesheet_id)"); + ttExecute("ALTER TABLE `tt_expense_items` ADD `timesheet_id` int(11) default NULL AFTER `project_id`"); + ttExecute("create index timesheet_idx on tt_expense_items(timesheet_id)"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.37', modified = now() where param_name = 'version_db' and param_value = '1.18.36'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.37') set rights = 'track_own_time,track_own_expenses,view_own_reports,view_own_timesheets,manage_own_timesheets,view_own_charts,view_own_invoices,view_own_projects,view_own_tasks,manage_own_settings,view_users,track_time,track_expenses,view_reports,view_timesheets,manage_timesheets,approve_timesheets,view_charts,view_own_clients,override_punch_mode,override_own_punch_mode,override_date_lock,override_own_date_lock,swap_roles,manage_own_account,manage_users,manage_projects,manage_tasks,manage_custom_fields,manage_clients,manage_invoices,override_allow_ip,manage_basic_settings,view_all_reports,manage_features,manage_advanced_settings,manage_roles,export_data,manage_subgroups,delete_group' where `rank` = 512"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.37') set rights = replace(rights, 'view_own_reports,view_own_charts', 'view_own_reports,view_own_timesheets,view_own_charts') where `rank` = 16"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.37') set rights = replace(rights, 'view_own_reports,view_own_charts', 'view_own_reports,view_own_timesheets,manage_own_timesheets,view_own_charts') where `rank` = 4 or `rank` = 12 or `rank` = 68 or `rank` = 324"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.37') set rights = replace(rights, 'view_reports,view_charts', 'view_reports,view_timesheets,manage_timesheets,approve_timesheets,view_charts') where `rank` = 12 or `rank` = 68 or `rank` = 324"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.37') set rights = replace(rights, 'swap_roles,approve_timesheets', 'swap_roles') where `rank` = 12 or `rank` = 68 or `rank` = 324"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.38', modified = now() where param_name = 'version_db' and param_value = '1.18.37'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.38') set rights = 'track_own_time,track_own_expenses,view_own_reports,view_own_timesheets,manage_own_timesheets,view_own_charts,view_own_invoices,view_own_projects,view_own_tasks,manage_own_settings,view_users,track_time,track_expenses,view_reports,view_timesheets,manage_timesheets,approve_timesheets,view_charts,view_own_clients,override_punch_mode,override_own_punch_mode,override_date_lock,override_own_date_lock,swap_roles,manage_own_account,manage_users,manage_projects,manage_tasks,manage_custom_fields,manage_clients,manage_invoices,override_allow_ip,manage_basic_settings,view_all_reports,view_all_timesheets,manage_all_timesheets,manage_features,manage_advanced_settings,manage_roles,export_data,approve_all_timesheets,manage_subgroups,delete_group' where `rank` = 512"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.38') set rights = replace(rights, 'view_all_reports', 'view_all_reports,view_all_timesheets,manage_all_timesheets') where `rank` = 68 or `rank` = 324"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.38') set rights = replace(rights, 'export_data,manage_subgroups', 'export_data,approve_all_timesheets,manage_subgroups') where `rank` = 324"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.39', modified = now() where param_name = 'version_db' and param_value = '1.18.38'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.39') set rights = 'track_own_time,track_own_expenses,view_own_reports,view_own_timesheets,manage_own_timesheets,view_own_charts,view_own_projects,view_own_tasks,manage_own_settings,view_users,view_client_reports,view_client_timesheets,view_client_invoices,track_time,track_expenses,view_reports,view_timesheets,manage_timesheets,approve_timesheets,view_charts,view_own_clients,override_punch_mode,override_own_punch_mode,override_date_lock,override_own_date_lock,swap_roles,manage_own_account,manage_users,manage_projects,manage_tasks,manage_custom_fields,manage_clients,manage_invoices,override_allow_ip,manage_basic_settings,view_all_reports,view_all_timesheets,manage_all_timesheets,manage_features,manage_advanced_settings,manage_roles,export_data,approve_all_timesheets,manage_subgroups,delete_group' where `rank` = 512"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.39') set rights = replace(rights, 'view_own_reports', 'view_client_reports') where `rank` = 16"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.39') set rights = replace(rights, 'view_own_charts,', '') where `rank` = 16"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.39') set rights = replace(rights, 'view_own_timesheets', 'view_client_timesheets') where `rank` = 16"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.39') set rights = replace(rights, 'view_own_invoices', 'view_client_invoices') where `rank` = 16"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.40', modified = now() where param_name = 'version_db' and param_value = '1.18.39'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.40') set rights = replace(rights, 'view_client_timesheets,view_client_invoices', 'view_client_timesheets,view_client_unapproved,view_client_invoices') where `rank` = 16"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.41', modified = now() where param_name = 'version_db' and param_value = '1.18.40'"); + ttExecute("ALTER TABLE `tt_log` ADD `approved` tinyint(4) default 0 AFTER `billable`"); + ttExecute("ALTER TABLE `tt_expense_items` ADD `approved` tinyint(4) default 0 AFTER `invoice_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.42', modified = now() where param_name = 'version_db' and param_value = '1.18.41'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.42') set rights = 'track_own_time,track_own_expenses,view_own_reports,view_own_timesheets,manage_own_timesheets,view_own_charts,view_own_projects,view_own_tasks,manage_own_settings,view_users,view_client_reports,view_client_timesheets,view_client_unapproved,view_client_invoices,track_time,track_expenses,view_reports,approve_reports,view_timesheets,manage_timesheets,approve_timesheets,view_charts,view_own_clients,override_punch_mode,override_own_punch_mode,override_date_lock,override_own_date_lock,swap_roles,manage_own_account,manage_users,manage_projects,manage_tasks,manage_custom_fields,manage_clients,manage_invoices,override_allow_ip,manage_basic_settings,view_all_reports,view_all_timesheets,manage_all_timesheets,manage_features,manage_advanced_settings,manage_roles,export_data,approve_all_reports,approve_all_timesheets,manage_subgroups,delete_group' where `rank` = 512"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.42') set rights = replace(rights, 'view_reports,view_timesheets', 'view_reports,approve_reports,view_timesheets') where `rank` = 12 or `rank` = 68 or `rank` = 324"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.42') set rights = replace(rights, 'export_data,approve_all_timesheets', 'export_data,approve_all_reports,approve_all_timesheets') where `rank` = 324"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.43', modified = now() where param_name = 'version_db' and param_value = '1.18.42'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.43') set rights = 'track_own_time,track_own_expenses,view_own_reports,view_own_timesheets,manage_own_timesheets,view_own_charts,view_own_projects,view_own_tasks,manage_own_settings,view_users,view_client_reports,view_client_timesheets,view_client_invoices,track_time,track_expenses,view_reports,approve_reports,view_timesheets,manage_timesheets,approve_timesheets,view_charts,view_own_clients,override_punch_mode,override_own_punch_mode,override_date_lock,override_own_date_lock,swap_roles,manage_own_account,manage_users,manage_projects,manage_tasks,manage_custom_fields,manage_clients,manage_invoices,override_allow_ip,manage_basic_settings,view_all_reports,view_all_timesheets,manage_all_timesheets,manage_features,manage_advanced_settings,manage_roles,export_data,approve_all_reports,approve_all_timesheets,manage_subgroups,view_client_unapproved,delete_group' where `rank` = 512"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.43') set rights = replace(rights, 'view_client_timesheets,view_client_unapproved,view_client_invoices', 'view_client_timesheets,view_client_invoices') where `rank` = 16"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.44', modified = now() where param_name = 'version_db' and param_value = '1.18.43'"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `approved` tinyint(4) default NULL AFTER `billable`"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `timesheet` tinyint(4) default NULL AFTER `invoice`"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `show_timesheet` tinyint(4) NOT NULL default 0 AFTER `show_project`"); + ttExecute("ALTER TABLE `tt_fav_reports` ADD `show_approved` tinyint(4) NOT NULL default 0 AFTER `show_note`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.45', modified = now() where param_name = 'version_db' and param_value = '1.18.44'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.45') set rights = replace(rights, 'view_client_timesheets,', '')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.46', modified = now() where param_name = 'version_db' and param_value = '1.18.45'"); + ttExecute("ALTER TABLE `tt_timesheets` ADD `comment` text AFTER `name`"); + ttExecute("ALTER TABLE `tt_timesheets` ADD `start_date` date NOT NULL AFTER `comment`"); + ttExecute("ALTER TABLE `tt_timesheets` ADD `end_date` date NOT NULL AFTER `start_date`"); + ttExecute("ALTER TABLE `tt_timesheets` DROP `submitter_comment`"); + ttExecute("ALTER TABLE `tt_timesheets` CHANGE `approval_status` `approve_status` tinyint(4) default NULL"); + ttExecute("ALTER TABLE `tt_timesheets` CHANGE `manager_comment` `approve_comment` text"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.47', modified = now() where param_name = 'version_db' and param_value = '1.18.46'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.47') set rights = replace(rights, 'view_own_timesheets,', '')"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.47') set rights = replace(rights, 'view_timesheets,', '')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.48', modified = now() where param_name = 'version_db' and param_value = '1.18.47'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.48') set rights = replace(rights, 'manage_own_timesheets,', '')"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.48') set rights = replace(rights, 'manage_timesheets,', '')"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.48') set rights = replace(rights, 'view_all_timesheets,', '')"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.48') set rights = replace(rights, 'manage_all_timesheets,', '')"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.48') set rights = replace(rights, 'approve_all_timesheets,', 'approve_own_timesheets,')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.49', modified = now() where param_name = 'version_db' and param_value = '1.18.48'"); + ttExecute("ALTER TABLE `tt_timesheets` ADD `project_id` int(11) default NULL AFTER `client_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.50', modified = now() where param_name = 'version_db' and param_value = '1.18.49'"); + ttExecute("drop index timesheet_idx on tt_expense_items"); + ttExecute("ALTER TABLE `tt_expense_items` DROP `timesheet_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.51', modified = now() where param_name = 'version_db' and param_value = '1.18.50'"); + ttExecute("CREATE TABLE `tt_templates` (`id` int(11) NOT NULL auto_increment,`group_id` int(11) default NULL,`org_id` int(11) default NULL,`name` varchar(80) COLLATE utf8mb4_bin NOT NULL,`content` text,`created` datetime default NULL,`created_ip` varchar(45) default NULL,`created_by` int(11) default NULL,`modified` datetime default NULL,`modified_ip` varchar(45) default NULL,`modified_by` int(11) default NULL,`status` tinyint(4) default 1,PRIMARY KEY (`id`))"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.54', modified = now() where param_name = 'version_db' and param_value = '1.18.51'"); + ttExecute("ALTER TABLE `tt_templates` ADD `description` varchar(255) default NULL AFTER `name`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.55', modified = now() where param_name = 'version_db' and param_value = '1.18.54'"); + ttExecute("CREATE TABLE `tt_files` (`id` int(10) unsigned NOT NULL auto_increment,`group_id` int(10) unsigned,`org_id` int(10) unsigned,`remote_id` bigint(20) unsigned,`entity_type` varchar(32),`entity_id` int(10) unsigned,`file_name` varchar(80) COLLATE utf8mb4_bin NOT NULL,`description` varchar(255) default NULL,`created` datetime default NULL,`created_ip` varchar(45) default NULL,`created_by` int(10) unsigned,`modified` datetime default NULL,`modified_ip` varchar(45) default NULL,`modified_by` int(10) unsigned,`status` tinyint(1) default 1,PRIMARY KEY (`id`))"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.59', modified = now() where param_name = 'version_db' and param_value = '1.18.55'"); + ttExecute("ALTER TABLE `tt_files` ADD `file_key` varchar(32) AFTER `remote_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.60', modified = now() where param_name = 'version_db' and param_value = '1.18.59'"); + ttExecute("ALTER TABLE `tt_groups` ADD `group_key` varchar(32) AFTER `org_id`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.18.61', modified = now() where param_name = 'version_db' and param_value = '1.18.60'"); + ttGenerateKeys(); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.61') set rights = replace(rights, 'swap_roles', 'swap_roles,update_work') where `rank` >= 12"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.18.61') set rights = replace(rights, 'view_all_reports', 'view_all_reports,manage_work,bid_on_work') where `rank` >= 68"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.19.0', modified = now() where param_name = 'version_db' and param_value = '1.18.61'"); + } + + if (array_key_exists('convert11900to12203', $_POST)) { + ttExecute("CREATE TABLE `tt_work_currencies` (`id` int(10) unsigned NOT NULL,`name` varchar(10) NOT NULL,PRIMARY KEY (`id`))"); + ttExecute("create unique index currency_idx on tt_work_currencies(`name`)"); + ttExecute("INSERT INTO `tt_work_currencies` (`id`, `name`) VALUES ('1', 'USD'), ('2', 'CAD'), ('3', 'AUD'), ('4', 'EUR'), ('5', 'NZD')"); + ttExecute("CREATE TABLE `tt_work_categories` (`id` int(10) unsigned NOT NULL, `parents` text default NULL, `name` varchar(64) NOT NULL, `name_localized` text default NULL, PRIMARY KEY (`id`))"); + ttExecute("INSERT INTO `tt_work_categories` (`id`, `parents`, `name`, `name_localized`) VALUES ('1', NULL, 'Coding', 'es:Codificación,ru:Кодирование')"); + ttExecute("INSERT INTO `tt_work_categories` (`id`, `parents`, `name`, `name_localized`) VALUES ('2', NULL, 'Other', 'es:Otra,ru:Другое')"); + ttExecute("INSERT INTO `tt_work_categories` (`id`, `parents`, `name`, `name_localized`) VALUES ('3', '1', 'PHP', NULL)"); + ttExecute("INSERT INTO `tt_work_categories` (`id`, `parents`, `name`, `name_localized`) VALUES ('4', '1', 'C/C++', NULL)"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.19.3', modified = now() where param_name = 'version_db' and param_value = '1.19.0'"); + ttExecute("ALTER TABLE `tt_groups` ADD `holidays` text default null AFTER `lock_spec`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.19.4', modified = now() where param_name = 'version_db' and param_value = '1.19.3'"); + ttExecute("ALTER TABLE `tt_custom_fields` ADD `entity_type` varchar(32) NOT NULL default 'time' AFTER `org_id`"); + ttExecute("update `tt_custom_fields` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.19.4') set entity_type = 1 where entity_type = 'time'"); + ttExecute("ALTER TABLE `tt_custom_fields` modify entity_type tinyint(4) NOT NULL default '1'"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.19.6', modified = now() where param_name = 'version_db' and param_value = '1.19.4'"); + ttExecute("CREATE TABLE `tt_entity_custom_fields` (`id` int(10) unsigned NOT NULL auto_increment,`group_id` int(10) unsigned NOT NULL,`org_id` int(10) unsigned NOT NULL,`entity_type` tinyint(4) NOT NULL,`entity_id` int(10) unsigned NOT NULL,`field_id` int(10) unsigned NOT NULL,`option_id` int(10) unsigned default NULL,`value` varchar(255) default NULL,`created` datetime default NULL,`created_ip` varchar(45) default NULL,`created_by` int(10) unsigned default NULL,`modified` datetime default NULL,`modified_ip` varchar(45) default NULL,`modified_by` int(10) unsigned default NULL,`status` tinyint(4) default 1,PRIMARY KEY (`id`))"); + ttExecute("create unique index entity_idx on tt_entity_custom_fields(entity_type, entity_id, field_id)"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.19.7', modified = now() where param_name = 'version_db' and param_value = '1.19.6'"); + ttExecute("ALTER TABLE `tt_fav_reports` drop `cf_1_option_id`"); + ttExecute("ALTER TABLE `tt_fav_reports` drop `show_custom_field_1`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.19.14', modified = now() where param_name = 'version_db' and param_value = '1.19.7'"); + ttExecute("CREATE TABLE `tt_project_template_binds` (`project_id` int(10) unsigned NOT NULL,`template_id` int(10) unsigned NOT NULL,`group_id` int(10) unsigned NOT NULL,`org_id` int(10) unsigned NOT NULL)"); + ttExecute("create index project_idx on tt_project_template_binds(project_id);"); + ttExecute("create index template_idx on tt_project_template_binds(template_id)"); + ttExecute("create unique index project_template_idx on tt_project_template_binds(project_id, template_id)"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.19.17', modified = now() where param_name = 'version_db' and param_value = '1.19.14'"); + ttExecute("ALTER TABLE `tt_groups` ADD `custom_css` text default NULL AFTER `config`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.19.19', modified = now() where param_name = 'version_db' and param_value = '1.19.17'"); + ttExecute("ALTER TABLE `tt_cron` ADD `comment` text AFTER `subject`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.19.22', modified = now() where param_name = 'version_db' and param_value = '1.19.19'"); + ttExecute("ALTER TABLE `tt_groups` drop `task_required`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.19.23', modified = now() where param_name = 'version_db' and param_value = '1.19.22'"); + ttExecute("ALTER TABLE `tt_groups` ADD `entities_modified` datetime default NULL AFTER `modified_by`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.19.29', modified = now() where param_name = 'version_db' and param_value = '1.19.23'"); + ttExecute("DROP TABLE `tt_work_currencies`"); + ttExecute("DROP TABLE `tt_work_categories`"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.19.29') set rights = replace(rights, 'swap_roles,update_work', 'swap_roles') where `rank` >= 12"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.19.29') set rights = replace(rights, 'view_all_reports,manage_work,bid_on_work', 'view_all_reports') where `rank` >= 68"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.20.0', modified = now() where param_name = 'version_db' and param_value = '1.19.29'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.20.0') set rights = replace(rights, 'manage_basic_settings,view_all_reports', 'manage_basic_settings,view_all_charts,view_all_reports') where `rank` >= 68"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.21.0', modified = now() where param_name = 'version_db' and param_value = '1.20.0'"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.21.0') set rights = replace(rights, 'view_client_unapproved,delete_group', 'view_client_unapproved,override_2fa,delete_group') where `rank` = 512"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.21.0') set rights = replace(rights, ',manage_work', '')"); + ttExecute("update `tt_roles` inner join `tt_site_config` sc on (sc.param_name = 'version_db' and sc.param_value = '1.21.0') set rights = replace(rights, ',update_work', '')"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.21.2', modified = now() where param_name = 'version_db' and param_value = '1.21.0'"); + ttExecute("ALTER TABLE tt_fav_reports ADD COLUMN `note_containing` varchar(80) default NULL after `paid_status`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.21.7', modified = now() where param_name = 'version_db' and param_value = '1.21.2'"); + ttExecute("ALTER TABLE `tt_groups` ADD `custom_translation` text default NULL AFTER `custom_css`"); + ttExecute("UPDATE `tt_site_config` SET param_value = '1.22.3', modified = now() where param_name = 'version_db' and param_value = '1.21.7'"); + } + + if (array_key_exists('cleanup', $_POST)) { + $mdb2 = getConnection(); + $inactive_orgs = ttOrgHelper::getInactiveOrgs(); + + $count = count($inactive_orgs); + print "$count inactive organizations found...
\n"; for ($i = 0; $i < $count; $i++) { - print " deleting team ".$inactive_teams[$i]."
\n"; - $res = ttTeamHelper::delete($inactive_teams[$i]); + print " deleting organization ".$inactive_orgs[$i]."
\n"; + $res = ttOrgHelper::deleteOrg($inactive_orgs[$i]); } - setChange("OPTIMIZE TABLE tt_client_project_binds"); - setChange("OPTIMIZE TABLE tt_clients"); - setChange("OPTIMIZE TABLE tt_config"); - setChange("OPTIMIZE TABLE tt_custom_field_log"); - setChange("OPTIMIZE TABLE tt_custom_field_options"); - setChange("OPTIMIZE TABLE tt_custom_fields"); - setChange("OPTIMIZE TABLE tt_expense_items"); - setChange("OPTIMIZE TABLE tt_fav_reports"); - setChange("OPTIMIZE TABLE tt_invoices"); - setChange("OPTIMIZE TABLE tt_log"); - setChange("OPTIMIZE TABLE tt_monthly_quotas"); - setChange("OPTIMIZE TABLE tt_project_task_binds"); - setChange("OPTIMIZE TABLE tt_projects"); - setChange("OPTIMIZE TABLE tt_tasks"); - setChange("OPTIMIZE TABLE tt_teams"); - setChange("OPTIMIZE TABLE tt_tmp_refs"); - setChange("OPTIMIZE TABLE tt_user_project_binds"); - setChange("OPTIMIZE TABLE tt_users"); - } - - print "done.
\n"; + // Delete items that were marked as deleted by users. + // This is currently commented out to ease up recovery of accidental deletes by users. + // These statements can be executed manually in MySQL console to shrink database a bit. + // ttExecute("delete from tt_log where status is null"); + // ttExecute("delete from tt_custom_field_log where status is null"); + // ttExecute("delete from tt_expense_items where status is null"); + + ttExecute("OPTIMIZE TABLE tt_client_project_binds"); + ttExecute("OPTIMIZE TABLE tt_clients"); + ttExecute("OPTIMIZE TABLE tt_config"); + ttExecute("OPTIMIZE TABLE tt_cron"); + ttExecute("OPTIMIZE TABLE tt_timesheets"); + ttExecute("OPTIMIZE TABLE tt_custom_field_log"); + ttExecute("OPTIMIZE TABLE tt_custom_field_options"); + ttExecute("OPTIMIZE TABLE tt_custom_fields"); + ttExecute("OPTIMIZE TABLE tt_expense_items"); + ttExecute("OPTIMIZE TABLE tt_fav_reports"); + ttExecute("OPTIMIZE TABLE tt_invoices"); + ttExecute("OPTIMIZE TABLE tt_log"); // This locks the production table for 4 minutes. Impossible to login, etc. + // TODO: what should we do about it? + ttExecute("OPTIMIZE TABLE tt_monthly_quotas"); + ttExecute("OPTIMIZE TABLE tt_templates"); + ttExecute("OPTIMIZE TABLE tt_project_template_binds"); + ttExecute("OPTIMIZE TABLE tt_predefined_expenses"); + ttExecute("OPTIMIZE TABLE tt_project_task_binds"); + ttExecute("OPTIMIZE TABLE tt_projects"); + ttExecute("OPTIMIZE TABLE tt_tasks"); + ttExecute("OPTIMIZE TABLE tt_groups"); + ttExecute("OPTIMIZE TABLE tt_tmp_refs"); + ttExecute("OPTIMIZE TABLE tt_user_project_binds"); + ttExecute("OPTIMIZE TABLE tt_users"); + ttExecute("OPTIMIZE TABLE tt_roles"); + ttExecute("OPTIMIZE TABLE tt_files"); + } + + print "Done.
\n"; } ?> @@ -741,7 +1258,7 @@ function setChange($sql) {

DB Install

-
Create database structure (v1.9.30) + Create database structure (v1.22.3)
(applies only to new installations, do not execute when updating)
@@ -773,8 +1290,25 @@ function setChange($sql) {


- Update database structure (v1.6 to v1.9) -
+ Update database structure (v1.6 to v1.14) +
+ + + Update database structure (v1.14 to v1.17.44) +
+ + + Update database structure (v1.17.44 to v1.17.97) +
+ + + + Update database structure (v1.17.97 to v1.19) + + + + Update database structure (v1.19 to v1.22.3) + diff --git a/default.css b/default.css index b1b7ec366..d1a77ebd8 100644 --- a/default.css +++ b/default.css @@ -1,32 +1,629 @@ -/* -// +----------------------------------------------------------------------+ -// | Anuko Time Tracker -// +----------------------------------------------------------------------+ -// | Copyright (c) Anuko International Ltd. (https://www.anuko.com) -// +----------------------------------------------------------------------+ -// | LIBERAL FREEWARE LICENSE: This source code document may be used -// | by anyone for any purpose, and freely redistributed alone or in -// | combination with other software, provided that the license is obeyed. -// | -// | There are only two ways to violate the license: -// | -// | 1. To redistribute this code in source form, with the copyright -// | notice or license removed or altered. (Distributing in compiled -// | forms without embedded copyright notices is permitted). -// | -// | 2. To redistribute modified versions of this code in *any* form -// | that bears insufficient indications that the modifications are -// | not the work of the original author(s). -// | -// | This license applies to this document only, not any other software -// | that it may be combined with. -// | -// +----------------------------------------------------------------------+ -// | Contributors: -// | https://www.anuko.com/time_tracker/credits.htm -// +----------------------------------------------------------------------+ -*/ +/* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt */ +body { + margin: 0px; + font-size: 10pt; + font-family: Verdana, sans-serif; + background-color: white; +} + +/* logo on top of pages */ +div.logo { + padding: .5rem; + text-align: center; + background-color: #a6ccf7; +} + +/* top menu bar for large screens */ +div.top-menu-large-screen { + background-color: black; + display: block; +} + +/* submenu for large screens */ +div.top-submenu-large-screen { + background-color: #d9d9d9; + display: block; +} + +/* top menu for small screens */ +div.top-menu-small-screen { + background-color: black; + display: none; +} + +/* top menu items */ +div.top-menu-large-screen a, div.top-menu-small-screen a { + font-size: 1rem; + font-weight: bold; + color: white; + margin: .1rem .2rem .1rem .2rem; +} + +/* top menu tables */ +.top-menu-table, .top-submenu-table { + margin-left: auto; + margin-right: auto; +} + +/* top submenu items */ +.top-submenu-large-screen a { + font-size: 1rem; + color: #444444; + margin: .1rem .2rem .1rem .2rem; +} + +/* page title div */ +div.page-title { + font-size: 12pt; + font-weight: bold; + color: silver; + text-align: center; + padding: .25rem; + border-bottom: 1px solid lightgray; + margin-bottom: .2rem; +} + +/* user details div */ +div.user-details { + text-align: center; + padding: .2rem; +} + +/* page errors div */ +.page-errors { + margin-top: .5rem; + text-align: center; + color: red; +} + +/* page messages div */ +.page-messages { + margin-top: .5rem; + text-align: center; + color: blue; +} + +/* page content div */ +.page-content { + text-align: center; +} + +/* vertical separator for controls on forms for both large and small screens */ +.form-control-separator { + height: .5rem; +} + +/* vertical separator for controls on small screens */ +.small-screen-form-control-separator { + height: .25rem; + display: none; +} + +/* "about" text div on login page */ +div#LoginAboutText { + padding: 1rem; + margin-left:auto; + margin-right:auto; +} + +/* default domain text div for ldap login */ +#ldapDefaultDomain { + color: #777777; +} + +/* page hint div to display on top of some pages to clarify things */ +div.page-hint { + margin: .5rem; +} + +/* project rate table for user add and user edit pages */ +.project-rate-table { + width: 300px; + border-collapse: collapse; +} + +.project-rate-table td { + padding: .2rem; +} + +/* a table to display controls centered horizontally */ +.centered-table { + display: inline-block; + margin-top: .5rem; + margin-left: auto; + margin-right: auto; +} + +/* a table to hold things that may not fit into available screen width */ +.x-scrollable-table { + display: inline-block; + margin-top: .5rem; + margin-left: auto; + margin-right: auto; + border-collapse: collapse; +} + +/* For printing large tables that do not fit into one page, we have to change the display +property to "block" and also use "break-before: avoid;". This eliminates a page break between +header and large table on print previews. I could not accomplish this with inline-block +because break-before is apparently ignored for inline-block elements. +inline-block above in itself is needed for nice horizontal centering on screen. +A side effect of this is that printed tables are not horizontally centered. */ +@media print +{ + .x-scrollable-table { display: block; break-before: avoid; break-after: auto; } + /* Not sure if we need this, keep commented out for now. + .x-scrollable-table tr { page-break-inside:avoid; page-break-after:auto; } + .x-scrollable-table td { page-break-inside:avoid; page-break-after:auto; } */ +} + +/* th in x-scrollable-table */ +.x-scrollable-table th { + white-space: nowrap; + padding: .25rem; +} + +/* td in x-scrollable-table */ +.x-scrollable-table td { + padding: .25rem; +} + +/* alternating row background in x-scrollable-table */ +.x-scrollable-table tr:nth-child(even) {background: #ffffff} +.x-scrollable-table tr:nth-child(odd) {background: #f5f5f5} + +/* a text cell in tables */ +.text-cell { + text-align: left; +} + +/* time cell in tables */ +.time-cell { + text-align: right; +} + +/* number cell in tables */ +.number-cell { + text-align: right; +} + +/* date cell in tables */ +.date-cell { + white-space: nowrap; +} + +/* money value cell in tables */ +.money-value-cell { + text-align: right; + white-space: nowrap; +} + +/* yes-no cell in tables */ +.yes-no-cell { + text-align: middle; +} + +/* text cell for note header when we display notes on separate rows */ +.note-header-cell { + text-align: right; +} + +/* cell containing text labels */ +.label-cell { + text-align: right; +} + +/* cell containign a subtotal */ +.subtotal-cell { + font-weight: bold; +} + +/* control labels for large screens */ +.large-screen-label { + text-align: right; + vertical-align: middle; + display: table-cell; +} + +/* control labels for small screens */ +.small-screen-label { + text-align: left; + display: none; +} + +/* list of items displayed in a chart on large screens */ +.large-screen-chart-items-list { + display: table-cell; +} + +/* list of items displayed in a chart on small screens */ +.small-screen-chart-items-list { + display: none; +} + +/* a div to display a chart in */ +div.chart { + padding-left: 1rem; + padding-right: 1rem; +} + +/* a table to annotate items from chart picture */ +.chart-items-list-table { + border-spacing: .2rem; +} + +/* a div to display a list of records in */ +div.record-list { + margin: 1rem; +} + +/* input controls */ +.page-content input { + border-radius: 4px; + border: 1px solid #c9c9c9; + padding: .4rem; +} + +/* input controls with focus on them */ +.page-content input:focus { + background-color: yellow; + border: 1px solid black; + outline: none; +} + +/* a table cell containing input control */ +.td-with-input { + text-align: left; +} + +/* a table cell containing input control */ +.td-with-horizontally-centered-input { + text-align: middle; +} + +/* a table cell containing a set ov vertically stacked checkboxes */ +.td-with-checkboxes { + text-align: left; + width: 300px; +} + +/* div containing how to contribute message */ +div.contribute-msg { + text-align: center; + margin-top: 2rem; + padding: .2rem; + background-color: #eeeeee; +} + +/* div containing app version and copyright info */ +div.version-and-copyright { + text-align: center; + padding: .5rem; +} + +/* img in td in centered-table, ex: now icons */ +.centered-table td img { + vertical-align: middle; +} + +/* span for what is it icon image */ +span.what-is-it-img { + display: none; +} + +/* span for what is it text */ +span.what-is-it-text { + display: inline-block; + vertical-align: middle; +} + +/* span for what format example */ +span.format-example { + color: #777777; +} + +/* buttons */ +input[type="submit"], input[type="button"] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +/* input controls */ +input[type="text"], input[type="password"], input[type="file"] { + width: 220px; +} + +/* text field input with an associated hint */ +input.text-field-with-hint[type="text"] { + width: 136px; +} + +/* project rate input control */ +input[type="text"].project-rate-field { + width: 50px; +} + +/* project rate input control */ +input[type="text"].quota-field { + width: 70px; +} + +/* project rate input control */ +input[type="text"].short-text-field { + width: 40px; +} + +/* dropdown control */ +.dropdown-field { + width: 236px; +} + +/* dropdown control with an associated format example */ +.dropdown-field-with-format-example { + width: 136px; +} + +/* dropdown control with an associated button */ +.dropdown-field-with-button { + width: 136px; +} + +/* a short dropdown control for small strings */ +.dropdown-field-short { + width: 85px; +} + +/* textareas for fields */ +#description, #address, #item_name, #comment, #custom_css, #custom_translation, #content { + width: 220px; + border-radius: 4px; + border: 1px solid #c9c9c9; + padding: .4rem; +} + +/* textareas with focus */ +#description:focus, #address:focus, #item_name:focus, #comment:focus, #custom_css:focus, #custom_translation:focus, #content:focus { + background-color: yellow; + border: 1px solid black; + outline: none; +} + +/* textarea for note field */ +#note { + width: 100%; + margin: 0; + height: 4rem; + resize: none; + border-radius: 4px; + border: 1px solid #c9c9c9; + padding: .4rem; +} + +/* note field with focus */ +#note:focus { + background-color: yellow; + border: 1px solid black; + outline: none; +} + +/* chart image */ +.chart-image { + width: 300px; + height: 300px; +} + +/* chart color cell for annotations */ +.chart-color-cell { + width: .4rem; + height: 1rem; +} + +/* chart description cell for annotations */ +.chart-description-cell { + text-align: left; + padding-left: .25rem; +} + +/* calendar for small screens */ +.small-screen-calendar { + display: none; + width: 210px; + margin-top: 1rem; + margin-left: auto; + margin-right: auto; +} + +/* calendars with focus */ +.small-screen-calendar a:focus, .large-screen-calendar a:focus { + outline: none; +} + +/* calendar for large screens */ +.large-screen-calendar { + display: block; + width: 210px; + padding-left: 1rem; + margin-top: 1rem; +} + +/* label for checkboxes */ +.checkbox-label { + float: left; +} + +/* div for day totals */ +div.day-totals { + margin-bottom: 1rem; + width: 100%; +} + +/* column 1 for day totals */ +.day-totals-col1 { + text-align: left; + padding-left: .5rem; + padding-right: .5rem; +} + +/* column 2 for day totals */ +.day-totals-col2 { + padding-left: .5rem; + padding-right: .5rem; + text-align: right; +} + +/* not billable items */ +.not-billable { + color: red; +} + +/* remaining quota span */ +span.remaining-quota { + color: red; +} + +/* remaining quota balance span */ +span.remaining-quota-balance { + color: red; +} + +/* over quota span */ +span.over-quota { + color: green; +} + +/* over quota balance span */ +span.over-quota-balance { + color: green; +} + +/* div for optional navigation, ex: week view */ +div.optional-nav { + margin-top: .5rem; +} + +/* div for a set of buttons, ex: save, copy, delete */ +.button-set { + margin-top: .5rem; + margin-bottom: 1rem; +} + +/* section header div */ +div.section-header { + font-weight: bold; + text-align: center; + margin-left: auto; + margin-right: auto; + margin-top: .5rem; + margin-bottom: .5rem; +} + +/* invoice-header div */ +div.invoice-header { + font-size: 2rem; +} + +th.invoice-label { + text-align: right; +} + +/* div for a group of items in site map */ +div.sitemap-item-group { + margin-top: 2rem; + margin-bottom: 2rem; +} + +/* div for a single item in site map */ +div.sitemap-item { + margin: .4rem; +} + +div.punch-timer { + font-size: 2rem; + color: blue; +} + +/* media rules for small screens */ +@media screen and (max-width: 600px) { + + /* hide large screen labels */ + .large-screen-label { + display: none; + } + + /* hide large screen chart annotations */ + .large-screen-chart-items-list { + display: none; + } + + /* show small screen chart annotations */ + .small-screen-chart-items-list { + display: table-cell; + } + + /* show small screen labels */ + .small-screen-label { + display: block; + } + + /* show small screen calendar */ + .small-screen-calendar { + display: block; + } + + /* hide large screen calendar */ + .large-screen-calendar { + display: none; + } + + /* show small screen form control separators */ + .small-screen-form-control-separator { + display: block; + } + + /* show what is it icons */ + span.what-is-it-img { + display: inline-block; + } + + /* hide what is it text */ + span.what-is-it-text { + display: none; + } + + /* hide how to contribute message */ + div.contribute-msg { + display: none; + } + + /* hide version and copyright */ + div.version-and-copyright { + display: none; + } +} + +/* media rules for menus on small screens */ +@media screen and (max-width: 1170px) { + + /* show small screen menu */ + div.top-menu-small-screen { + display: block; + } + + /* hide large screen menu */ + div.top-menu-large-screen { + display: none; + } + + /* hide large screen submenu */ + div.top-submenu-large-screen { + display: none; + } +} + +/* TODO: below are legacy styles. Review as work on mobile-friendly pages continues. */ a { color: blue; text-decoration: none; } a:visited { text-decoration: none; } @@ -39,17 +636,22 @@ html { body { font-size: 10pt; - font-family: verdana; + font-family: Verdana, sans-serif; background-color: white; } -table { font-size: 10pt; font-family: verdana; } +table { font-size: 10pt; font-family: Verdana, sans-serif; } -input, button { font-size: 10pt; font-family: verdana; } - -textarea { font-size: 10pt; font-family: verdana; } +input, button, select, textarea { + font-size: 10pt; + font-family: Verdana, sans-serif; + margin: 2px 2px 2px 2px; + padding: 2px 5px 2px 5px; +} -select{ font-size: 10pt; font-family: verdana; } +input[type=checkbox], label { + vertical-align: middle; +} .pageTitle { font-size: 12pt; @@ -58,37 +660,110 @@ select{ font-size: 10pt; font-family: verdana; } } .systemMenu { - font-size: 12pt; + font-size: 1rem; font-weight: bold; - color: #ffffff; - background-color: #000000; + color: white; + background-color: black; + margin: .1rem .2rem .1rem .2rem; } .mainMenu { - font-size: 12pt; + font-size: 1rem; color: #444444; + margin: .1rem .2rem .1rem .2rem; +} + +.onBehalf { + font-weight: bold; +} + +/* calendar styles */ +.calendarHeader { + padding: 5px; + font-size: 8pt; + color: #333333; + background-color: #d9d9d9; +} + +.calendarDay { + padding: 5px; + border: 1px solid silver; + font-size: 8pt; + color: #333333; + background-color: #ffffff; +} + +.calendarDaySelected { + padding: 5px; + border: 1px solid silver; + font-size: 8pt; + color: #666666; + background-color: #a6ccf7; +} + +.calendarDayWeekend { + padding: 5px; + border: 1px solid silver; + font-size: 8pt; + color: #666666; + background-color: #f7f7f7; +} + +.calendarDayHoliday { + padding: 5px; + border: 1px solid silver; + font-size: 8pt; + color: #666666; + background-color: #f7f7f7; +} + +.calendarDayHeader { + padding: 5px; + border: 1px solid white; + font-size: 8pt; + color: #333333; +} + +.calendarDayHeaderWeekend { + padding: 5px; + border: 1px solid white; + font-size: 8pt; + color: #999999; +} + +.calendarLinkWeekend { + color: #999999; +} + +.calendarLinkHoliday { + color: #999999; +} + +.calendarLinkRecordsExist { + color: #ff0000; +} + +.calendarLinkNonCompleteDay { + color: #800080; } +/* end of calendar styles */ .tableHeader { font-weight: bold; text-align: left; - color: #000000; - background-color: #a6ccf7; } .tableHeaderCentered { font-weight: bold; text-align: center; - color: #000000; - background-color: #a6ccf7; } .rowReportItem { - background-color: #ccccce; + background-color: #f5f5f5; } .rowReportItemAlt { - background-color: #f5f5f5; + background-color: #ffffff; } .rowReportSubtotal { @@ -116,13 +791,13 @@ select{ font-size: 10pt; font-family: verdana; } vertical-align: top; } -.sectionHeader { - font-weight: bold; - border-bottom: 1px solid silver; +.midAligned { + text-align: center; } -.borderTop td { - border-top: 1px solid silver; +.sectionHeader { + font-weight: bold; + border-bottom: 1px solid lightgray; } .sectionHeaderNoBorder { @@ -130,23 +805,18 @@ select{ font-size: 10pt; font-family: verdana; } } .error { - font-weight: bold; color: red; } .info_message { - font-weight: bold; color: #0000c0; } .divider { background-color: #efefef; } -table.divider { - width: 720px; -} -div#LoginAboutText { width:400px; } +div.table-divider { height: 30px; } .uncompleted-entry { display: inline-block; @@ -155,8 +825,15 @@ div#LoginAboutText { width:400px; } border: 1px solid rgba(0, 0, 0, .1); border-radius: 50%; background-color: rgba(0, 0, 0, .1); + margin-right: .25em; +} +.uncompleted-entry.active-today { background-color: red; } +.uncompleted-entry.active { background-color: purple; } + +.table_icon { + height: 16px; + width: 16px; } -.uncompleted-entry.active { background-color: red; } /* Mobile styles */ .mobile-table { diff --git a/display_options.php b/display_options.php new file mode 100644 index 000000000..e0b7bc987 --- /dev/null +++ b/display_options.php @@ -0,0 +1,91 @@ +getConfigHelper(); + +if ($request->isPost()) { + $cl_time_note_on_separate_row = (bool)$request->getParameter('time_note_on_separate_row'); + $cl_time_not_complete_days = (bool)$request->getParameter('time_not_complete_days'); + $cl_record_custom_fields = (bool)$request->getParameter('record_custom_fields'); + $cl_report_note_on_separate_row = (bool)$request->getParameter('report_note_on_separate_row'); + $cl_report_inactive_projects = (bool)($request->getParameter('report_inactive_projects')); + $cl_report_cost_per_hour = (bool)$request->getParameter('report_cost_per_hour'); + $cl_custom_css = trim($request->getParameter('custom_css')); + $cl_custom_translation = trim($request->getParameter('custom_translation')); +} else { + $cl_time_note_on_separate_row = $config->getDefinedValue('time_note_on_separate_row'); + $cl_time_not_complete_days = $config->getDefinedValue('time_not_complete_days'); + $cl_record_custom_fields = $config->getDefinedValue('record_custom_fields'); + $cl_report_note_on_separate_row = $config->getDefinedValue('report_note_on_separate_row'); + $cl_report_inactive_projects = $config->getDefinedValue('report_inactive_projects'); + $cl_report_cost_per_hour = $config->getDefinedValue('report_cost_per_hour'); + $cl_custom_css = $user->getCustomCss(); + $cl_custom_translation = $user->getCustomTranslation(); +} + +$form = new Form('displayOptionsForm'); + +// Time page. +// $form->addInput(array('type'=>'checkbox','name'=>'time_client','value'=>$cl_time_client)); +// $form->addInput(array('type'=>'checkbox','name'=>'time_project','value'=>$cl_time_project)); +// $form->addInput(array('type'=>'checkbox','name'=>'time_task','value'=>$cl_time_task)); +// $form->addInput(array('type'=>'checkbox','name'=>'time_start','value'=>$cl_time_start)); +// $form->addInput(array('type'=>'checkbox','name'=>'time_finish','value'=>$cl_time_finish)); +// $form->addInput(array('type'=>'checkbox','name'=>'time_duration','value'=>$cl_time_duration)); +// $form->addInput(array('type'=>'checkbox','name'=>'time_note','value'=>$cl_time_note)); +$form->addInput(array('type'=>'checkbox','name'=>'time_note_on_separate_row','value'=>$cl_time_note_on_separate_row)); +$form->addInput(array('type'=>'checkbox','name'=>'time_not_complete_days','value'=>$cl_time_not_complete_days)); +$form->addInput(array('type'=>'checkbox','name'=>'record_custom_fields','value'=>$cl_record_custom_fields)); +// TODO: consider adding other fields (timesheet, work_units, invoice, approved, cost, paid)? + +// Reports. +$form->addInput(array('type'=>'checkbox','name'=>'report_note_on_separate_row','value'=>$cl_report_note_on_separate_row)); +$form->addInput(array('type'=>'checkbox','name'=>'report_inactive_projects','value'=>$cl_report_inactive_projects)); +$form->addInput(array('type'=>'checkbox','name'=>'report_cost_per_hour','value'=>$cl_report_cost_per_hour)); +// TODO: add PDF break controller here. + +$form->addInput(array('type'=>'textarea','name'=>'custom_css','value'=>$cl_custom_css)); +$form->addInput(array('type'=>'textarea','name'=>'custom_translation','value'=>$cl_custom_translation)); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); + +if ($request->isPost()){ + // Validate user input. + if (!ttValidCss($cl_custom_css)) $err->add($i18n->get('error.field'), $i18n->get('form.display_options.custom_css')); + if (!ttValidTranslation($cl_custom_translation)) $err->add($i18n->get('error.field'), $i18n->get('form.display_options.custom_translation')); + // Finished validating user input. + + if ($err->no()) { + // Update config. + $config->setDefinedValue('time_note_on_separate_row', $cl_time_note_on_separate_row); + $config->setDefinedValue('time_not_complete_days', $cl_time_not_complete_days); + $config->setDefinedValue('record_custom_fields', $cl_record_custom_fields); + $config->setDefinedValue('report_note_on_separate_row', $cl_report_note_on_separate_row); + $config->setDefinedValue('report_inactive_projects', $cl_report_inactive_projects); + $config->setDefinedValue('report_cost_per_hour', $cl_report_cost_per_hour); + if ($user->updateGroup(array( + 'config' => $config->getConfig(), + 'custom_css' => $cl_custom_css, + 'custom_translation' => $cl_custom_translation))) { + header('Location: success.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } +} + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.display_options')); +$smarty->assign('content_page_name', 'display_options.tpl'); +$smarty->display('index.tpl'); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..e621c4105 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,37 @@ +# This file is for development work. Not suitable for production. +version: '3.7' + +services: + # anuko_tt is a web application built as per dockerfile specification. + anuko_tt: + build: + context: . + dockerfile: dockerfile-tt + image: anuko_timetracker:dev + container_name: anuko-timetracker + # Use localhost:8080 to connect to timetracker via browser. + ports: + - "8080:80" + + # anuko_db is a mariadb instance to which timetracker connects. + # Connect parameters are also specified in timetracker dockerfile after + # creation of its configuration file. Specifically, we replace + # user name, password, service name (aka resolvable to IP server name + # where mariadb runs), and database name there from the defaults. + # These two sets of credentials must match for a successful connect. + anuko_db: + build: + context: . + dockerfile: dockerfile-db + image: "anuko_database:dev" + container_name: anuko-database + environment: + MYSQL_RANDOM_ROOT_PASSWORD: "yes" + MYSQL_DATABASE: timetracker + MYSQL_USER: anuko_user + MYSQL_PASSWORD: anuko_pw + volumes: + - anuko_db:/var/lib/mysql + +volumes: + anuko_db: \ No newline at end of file diff --git a/dockerfile-db b/dockerfile-db new file mode 100644 index 000000000..757aa0b30 --- /dev/null +++ b/dockerfile-db @@ -0,0 +1,5 @@ +# This file is for development work. Not suitable for production. +FROM mariadb:latest + +# Copy database creation script. +COPY mysql.sql /docker-entrypoint-initdb.d/ diff --git a/dockerfile-tt b/dockerfile-tt new file mode 100644 index 000000000..9c79f4786 --- /dev/null +++ b/dockerfile-tt @@ -0,0 +1,39 @@ +# This file is for development work. Not suitable for production. +FROM php:7.2-apache + +# Use the default production configuration. +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +# Override with custom settings. +# COPY config/php_tt.ini $PHP_INI_DIR/conf.d/ + +# Install mysqli extension. +RUN docker-php-ext-install mysqli + +# Install gd extension. +RUN apt-get update && apt-get install libpng-dev libfreetype6-dev -y \ + && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ \ + && docker-php-ext-install gd + +# Install ldap extension. +RUN apt-get install libldap2-dev -y \ + && docker-php-ext-install ldap +# TODO: check if ldap works, as the above is missing this step: +# && docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ && \ + +# Cleanup. The intention was to keep image size down. +# RUN rm -rf /var/lib/apt/lists/* +# +# The above does not work. Files are removed, but +# image files (zipped or not) are not getting smaller. Why? + +# Copy application source code to /var/www/html/. +COPY . /var/www/html/ +# Create configuration file. +RUN cp /var/www/html/WEB-INF/config.php.dist /var/www/html/WEB-INF/config.php +# Replace DSN value to something connectable to a Docker container running mariadb. +RUN sed -i "s|mysqli://root:no@localhost/dbname|mysqli://anuko_user:anuko_pw@anuko_db/timetracker|g" /var/www/html/WEB-INF/config.php +# Note that db is defined as anuko_db/timetracker where anuko_db is service name and timetracker is db name. +# See docker-compose.yml for details. + +RUN chown -R www-data /var/www/html/ diff --git a/expense_delete.php b/expense_delete.php index c5f53aa91..6dbf421a9 100644 --- a/expense_delete.php +++ b/expense_delete.php @@ -1,63 +1,46 @@ isPluginEnabled('ex')) { +// Access checks. +if (!(ttAccessAllowed('track_own_expenses') || ttAccessAllowed('track_expenses'))) { header('Location: access_denied.php'); exit(); } - -$cl_id = $request->getParameter('id'); -$expense_item = ttExpenseHelper::getItem($cl_id, $user->getActiveUser()); - -// Prohibit deleting invoiced records. -if ($expense_item['invoice_id']) die($i18n->getKey('error.sys')); +if (!$user->isPluginEnabled('ex')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_id = (int)$request->getParameter('id'); +// Get the expense item we are deleting. +$expense_item = ttExpenseHelper::getItem($cl_id); +if (!$expense_item || $expense_item['approved'] || $expense_item['invoice_id']) { + // Prohibit deleting not ours, approved, or invoiced items. + header('Location: access_denied.php'); + exit(); +} +// End of access checks. if ($request->isPost()) { if ($request->getParameter('delete_button')) { // Delete button pressed. // Determine if it is okay to delete the record. - $item_date = new DateAndTime(DB_DATEFORMAT, $expense_item['date']); + $item_date = new ttDate($expense_item['date']); if ($user->isDateLocked($item_date)) - $err->add($i18n->getKey('error.range_locked')); + $err->add($i18n->get('error.range_locked')); if ($err->no()) { // Mark the record as deleted. - if (ttExpenseHelper::markDeleted($cl_id, $user->getActiveUser())) { + if (ttExpenseHelper::markDeleted($cl_id)) { header('Location: expenses.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } if ($request->getParameter('cancel_button')) { // Cancel button pressed. @@ -68,11 +51,14 @@ $form = new Form('expenseItemForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); -$form->addInput(array('type'=>'submit','name'=>'delete_button','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'cancel_button','value'=>$i18n->getKey('button.cancel'))); +$form->addInput(array('type'=>'submit','name'=>'delete_button','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'cancel_button','value'=>$i18n->get('button.cancel'))); + +$show_project = MODE_PROJECTS == $user->getTrackingMode() || MODE_PROJECTS_AND_TASKS == $user->getTrackingMode(); -$smarty->assign('expense_item', $expense_item); $smarty->assign('forms', array($form->getName() => $form->toArray())); -$smarty->assign('title', $i18n->getKey('title.delete_expense')); +$smarty->assign('expense_item', $expense_item); +$smarty->assign('show_project', $show_project); +$smarty->assign('title', $i18n->get('title.delete_expense')); $smarty->assign('content_page_name', 'expense_delete.tpl'); $smarty->display('index.tpl'); diff --git a/expense_edit.php b/expense_edit.php index 7a256d653..98098a19f 100644 --- a/expense_edit.php +++ b/expense_edit.php @@ -1,101 +1,97 @@ isPluginEnabled('ex')) { +// Access checks. +if (!(ttAccessAllowed('track_own_expenses') || ttAccessAllowed('track_expenses'))) { header('Location: access_denied.php'); exit(); } - -$cl_id = $request->getParameter('id'); - +if (!$user->isPluginEnabled('ex')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_id = (int)$request->getParameter('id'); // Get the expense item we are editing. -$expense_item = ttExpenseHelper::getItem($cl_id, $user->getActiveUser()); - -// Prohibit editing invoiced items. -if ($expense_item['invoice_id']) die($i18n->getKey('error.sys')); +$expense_item = ttExpenseHelper::getItem($cl_id); +if (!$expense_item) $expense_item = ttExpenseHelper::getOnBehalfItem($cl_id); +if (!$expense_item || $expense_item['approved'] || $expense_item['invoice_id']) { + // Prohibit editing not ours, approved, or invoiced items. + header('Location: access_denied.php'); + exit(); +} +if ($request->isPost()) { + // Validate that browser_today parameter is in correct format. + $browser_today = $request->getParameter('browser_today'); + if ($browser_today && !ttValidDbDateFormatDate($browser_today)) { + header('Location: access_denied.php'); + exit(); + } +} +// End of access checks. -$item_date = new DateAndTime(DB_DATEFORMAT, $expense_item['date']); +$item_date = new ttDate($expense_item['date']); +$confirm_save = $user->getConfigOption('confirm_save'); +$trackingMode = $user->getTrackingMode(); +$show_project = MODE_PROJECTS == $trackingMode || MODE_PROJECTS_AND_TASKS == $trackingMode; // Initialize variables. -$cl_date = $cl_client = $cl_project = $cl_item_name = $cl_cost = null; +$cl_date = $cl_client = $cl_project = $cl_item_name = $cl_cost = $cl_paid = null; if ($request->isPost()) { $cl_date = trim($request->getParameter('date')); $cl_client = $request->getParameter('client'); $cl_project = $request->getParameter('project'); $cl_item_name = trim($request->getParameter('item_name')); $cl_cost = trim($request->getParameter('cost')); + if ($user->isPluginEnabled('ps')) + $cl_paid = (bool)$request->getParameter('paid'); } else { - $cl_date = $item_date->toString($user->date_format); + $cl_date = $item_date->toString($user->getDateFormat()); $cl_client = $expense_item['client_id']; $cl_project = $expense_item['project_id']; $cl_item_name = $expense_item['name']; $cl_cost = $expense_item['cost']; + $cl_paid = (bool)$expense_item['paid']; } // Initialize elements of 'expenseItemForm'. $form = new Form('expenseItemForm'); // Dropdown for clients in MODE_TIME. Use all active clients. -if (MODE_TIME == $user->tracking_mode && $user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); +if (MODE_TIME == $trackingMode && $user->isPluginEnabled('cl')) { + $active_clients = ttGroupHelper::getActiveClients(true); $form->addInput(array('type'=>'combobox', 'onchange'=>'fillProjectDropdown(this.value);', 'name'=>'client', - 'style'=>'width: 250px;', 'value'=>$cl_client, 'data'=>$active_clients, 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); // Note: in other modes the client list is filtered to relevant clients only. See below. } -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { +if ($show_project) { // Dropdown for projects assigned to user. $project_list = $user->getAssignedProjects(); $form->addInput(array('type'=>'combobox', 'name'=>'project', - 'style'=>'width: 250px;', 'value'=>$cl_project, 'data'=>$project_list, 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); // Dropdown for clients if the clients plugin is enabled. + $client_list = array(); if ($user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - // We need an array of assigned project ids to do some trimming. + $active_clients = ttGroupHelper::getActiveClients(true); + // We need an array of assigned project ids to do some trimming. foreach($project_list as $project) $projects_assigned_to_user[] = $project['id']; @@ -112,56 +108,60 @@ $form->addInput(array('type'=>'combobox', 'onchange'=>'fillProjectDropdown(this.value);', 'name'=>'client', - 'style'=>'width: 250px;', 'value'=>$cl_client, 'data'=>$client_list, 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); } } // If predefined expenses are configured, add controls to select an expense and quantity. -$predefined_expenses = ttTeamHelper::getPredefinedExpenses($user->team_id); +$predefined_expenses = ttGroupHelper::getPredefinedExpenses(); if ($predefined_expenses) { $form->addInput(array('type'=>'combobox', 'onchange'=>'recalculateCost();', 'name'=>'predefined_expense', - 'style'=>'width: 250px;', - 'value'=>$cl_predefined_expense, 'data'=>$predefined_expenses, 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - $form->addInput(array('type'=>'text','onchange'=>'recalculateCost();','maxlength'=>'40','name'=>'quantity','style'=>'width: 100px;','value'=>$cl_quantity)); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $form->addInput(array('type'=>'text','onchange'=>'recalculateCost();','maxlength'=>'40','name'=>'quantity')); } -$form->addInput(array('type'=>'textarea','maxlength'=>'800','name'=>'item_name','style'=>'width: 250px; height:'.NOTE_INPUT_HEIGHT.'px;','value'=>$cl_item_name)); -$form->addInput(array('type'=>'text','maxlength'=>'40','name'=>'cost','style'=>'width: 100px;','value'=>$cl_cost)); +$form->addInput(array('type'=>'textarea','maxlength'=>'800','name'=>'item_name','value'=>$cl_item_name)); +$form->addInput(array('type'=>'text','maxlength'=>'40','name'=>'cost','value'=>$cl_cost)); +if ($user->can('manage_invoices') && $user->isPluginEnabled('ps')) + $form->addInput(array('type'=>'checkbox','name'=>'paid','value'=>$cl_paid)); $form->addInput(array('type'=>'datefield','name'=>'date','maxlength'=>'20','value'=>$cl_date)); // Hidden control for record id. $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); $form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_save or btn_copy click. -$form->addInput(array('type'=>'submit','name'=>'btn_save','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_copy','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.copy'))); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); +$on_click_action = 'browser_today.value=get_date();'; +$form->addInput(array('type'=>'submit','name'=>'btn_copy','onclick'=>$on_click_action,'value'=>$i18n->get('button.copy'))); +if ($confirm_save) $on_click_action .= 'return(confirmSave());'; +$form->addInput(array('type'=>'submit','name'=>'btn_save','onclick'=>$on_click_action,'value'=>$i18n->get('button.save'))); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); if ($request->isPost()) { // Validate user input. - if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client) - $err->add($i18n->getKey('error.client')); - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $err->add($i18n->getKey('error.project')); - } - if (!ttValidString($cl_item_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.item')); - if (!ttValidFloat($cl_cost)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.cost')); - if (!ttValidDate($cl_date)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.date')); + if ($user->isPluginEnabled('cl') && $user->isOptionEnabled('client_required') && !$cl_client) + $err->add($i18n->get('error.client')); + if ($show_project && !$cl_project) + $err->add($i18n->get('error.project')); + if (!ttValidString($cl_item_name)) $err->add($i18n->get('error.field'), $i18n->get('label.item')); + if (!ttValidFloat($cl_cost)) $err->add($i18n->get('error.field'), $i18n->get('label.cost')); + if (!ttValidDate($cl_date)) $err->add($i18n->get('error.field'), $i18n->get('label.date')); // This is a new date for the expense item. - $new_date = new DateAndTime($user->date_format, $cl_date); + $new_date = new ttDate($cl_date, $user->getDateFormat()); // Prohibit creating entries in future. - if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($new_date->after($browser_today)) - $err->add($i18n->getKey('error.future_date')); + if (!$user->isOptionEnabled('future_entries')) { + $browser_today = new ttDate($request->getParameter('browser_today', null)); + $server_tomorrow = new ttDate(); + $server_tomorrow->incrementDay(); + if ($new_date->after($browser_today) || $new_date->after($server_tomorrow)) + $err->add($i18n->get('error.future_date')); } + if (!ttTimeHelper::canAdd()) $err->add($i18n->get('error.expired')); + // Finished validating user input. // Save record. if ($request->getParameter('btn_save')) { @@ -172,17 +172,17 @@ // Now, step by step. // 1) Prohibit saving locked entries in any form. if ($user->isDateLocked($item_date)) - $err->add($i18n->getKey('error.range_locked')); + $err->add($i18n->get('error.range_locked')); // 2) Prohibit saving unlocked entries into locked range. if ($err->no() && $user->isDateLocked($new_date)) - $err->add($i18n->getKey('error.range_locked')); + $err->add($i18n->get('error.range_locked')); // Now, an update. if ($err->no()) { - if (ttExpenseHelper::update(array('id'=>$cl_id,'date'=>$new_date->toString(DB_DATEFORMAT),'user_id'=>$user->getActiveUser(), - 'client_id'=>$cl_client,'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost))) { - header('Location: expenses.php?date='.$new_date->toString(DB_DATEFORMAT)); + if (ttExpenseHelper::update(array('id'=>$cl_id,'date'=>$new_date->toString(), + 'client_id'=>$cl_client,'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost,'paid'=>$cl_paid))) { + header('Location: expenses.php?date='.$new_date->toString()); exit(); } } @@ -192,16 +192,16 @@ if ($request->getParameter('btn_copy')) { // We need to prohibit saving into locked interval. if ($user->isDateLocked($new_date)) - $err->add($i18n->getKey('error.range_locked')); + $err->add($i18n->get('error.range_locked')); // Now, a new insert. if ($err->no()) { - if (ttExpenseHelper::insert(array('date'=>$new_date->toString(DB_DATEFORMAT),'user_id'=>$user->getActiveUser(), - 'client_id'=>$cl_client,'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost,'status'=>1))) { - header('Location: expenses.php?date='.$new_date->toString(DB_DATEFORMAT)); + if (ttExpenseHelper::insert(array('date'=>$new_date->toString(),'client_id'=>$cl_client, + 'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost,'status'=>1))) { + header('Location: expenses.php?date='.$new_date->toString()); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } @@ -211,11 +211,15 @@ } } // isPost +if ($confirm_save) { + $smarty->assign('confirm_save', true); + $smarty->assign('entry_date', $cl_date); +} +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('show_project', $show_project); $smarty->assign('predefined_expenses', $predefined_expenses); $smarty->assign('client_list', $client_list); $smarty->assign('project_list', $project_list); -$smarty->assign('task_list', $task_list); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.edit_expense')); +$smarty->assign('title', $i18n->get('title.edit_expense')); $smarty->assign('content_page_name', 'expense_edit.tpl'); $smarty->display('index.tpl'); diff --git a/expense_files.php b/expense_files.php new file mode 100644 index 000000000..7fe2a5d1a --- /dev/null +++ b/expense_files.php @@ -0,0 +1,65 @@ +isPluginEnabled('at')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_id = (int)$request->getParameter('id'); +$item = ttExpenseHelper::getItemForFileView($cl_id); +if (!$item) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +if ($request->isPost()) { + $cl_description = trim($request->getParameter('description')); +} + +$fileHelper = new ttFileHelper($err); +$files = $fileHelper::getEntityFiles($cl_id, 'expense'); + +$form = new Form('fileUploadForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); +$form->addInput(array('type'=>'upload','name'=>'newfile','value'=>$i18n->get('button.submit'))); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.add'))); + +if ($request->isPost()) { + // We are adding a new file. + + // Validate user input. + if (!$_FILES['newfile']['name']) $err->add($i18n->get('error.upload')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + // Finished validating user input. + + if ($err->no()) { + $fields = array('entity_type'=>'expense', + 'entity_id' => $cl_id, + 'file_name' => $_FILES['newfile']['name'], + 'description'=>$cl_description); + if ($fileHelper->putFile($fields)) { + header('Location: expense_files.php?id='.$cl_id); + exit(); + } + } +} // isPost + +$smarty->assign('can_edit', $item['can_edit']); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('files', $files); +$smarty->assign('title', $i18n->get('title.expense_files')); +$smarty->assign('content_page_name', 'entity_files.tpl'); +$smarty->display('index.tpl'); diff --git a/expenses.php b/expenses.php index 93161f085..3cbbd8bec 100644 --- a/expenses.php +++ b/expenses.php @@ -1,109 +1,143 @@ isPluginEnabled('ex')) { +// Access checks. +if (!(ttAccessAllowed('track_own_expenses') || ttAccessAllowed('track_expenses'))) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('ex')) { + header('Location: feature_disabled.php'); + exit(); +} +if (!$user->exists()) { + header('Location: access_denied.php'); // Nobody to enter expenses for. + exit(); +} +if ($user->behalf_id && (!$user->can('track_expenses') || !$user->checkBehalfId())) { + header('Location: access_denied.php'); // Trying on behalf, but no right or wrong user. + exit(); +} +if (!$user->behalf_id && !$user->can('track_own_expenses') && !$user->adjustBehalfId()) { + header('Location: access_denied.php'); // Trying as self, but no right for self, and noone to work on behalf. + exit(); +} +if ($request->isPost() && $request->getParameter('user')) { + $posted_user_id = $request->getParameter('user'); + if (!ttValidInteger($posted_user_id) && !$user->isUserValid($posted_user_id)) { + header('Location: access_denied.php'); // Wrong user id on post. + exit(); + } +} +// If we are passed in a date, make sure it is in correct format. +$date = $request->getParameter('date'); +if ($date && !ttValidDbDateFormatDate($date)) { + header('Location: access_denied.php'); + exit(); +} +if ($request->isPost()) { + // Validate that browser_today parameter is in correct format. + $browser_today = $request->getParameter('browser_today'); + if ($browser_today && !ttValidDbDateFormatDate($browser_today)) { + header('Location: access_denied.php'); + exit(); + } +} +// End of access checks. + +// Determine user for which we display this page. +$userChanged = (int)$request->getParameter('user_changed'); +if ($request->isPost() && $userChanged) { + $user_id = (int)$request->getParameter('user'); + $user->setOnBehalfUser($user_id); +} else { + $user_id = $user->getUser(); +} // Initialize and store date in session. $cl_date = $request->getParameter('date', @$_SESSION['date']); -$selected_date = new DateAndTime(DB_DATEFORMAT, $cl_date); -if($selected_date->isError()) - $selected_date = new DateAndTime(DB_DATEFORMAT); +$selected_date = new ttDate($cl_date); if(!$cl_date) - $cl_date = $selected_date->toString(DB_DATEFORMAT); + $cl_date = $selected_date->toString(); $_SESSION['date'] = $cl_date; + +$tracking_mode = $user->getTrackingMode(); +$show_project = MODE_PROJECTS == $tracking_mode || MODE_PROJECTS_AND_TASKS == $tracking_mode; +$showFiles = $user->isPluginEnabled('at'); + // Initialize variables. -$on_behalf_id = $request->getParameter('onBehalfUser', (isset($_SESSION['behalf_id']) ? $_SESSION['behalf_id'] : $user->id)); -$cl_client = $request->getParameter('client', ($request->getMethod()=='POST' ? null : @$_SESSION['client'])); +$cl_client = $request->getParameter('client', ($request->isPost() ? null : @$_SESSION['client'])); $_SESSION['client'] = $cl_client; -$cl_project = $request->getParameter('project', ($request->getMethod()=='POST' ? null : @$_SESSION['project'])); +$cl_project = $request->getParameter('project', ($request->isPost() ? null : @$_SESSION['project'])); $_SESSION['project'] = $cl_project; $cl_item_name = $request->getParameter('item_name'); $cl_cost = $request->getParameter('cost'); // Elements of expensesForm. $form = new Form('expensesForm'); - -if ($user->canManageTeam()) { - $user_list = ttTeamHelper::getActiveUsers(array('putSelfFirst'=>true)); - if (count($user_list) > 1) { +$largeScreenCalendarRowSpan = 1; // Number of rows calendar spans on large screens. + +if ($user->can('track_expenses')) { + $rank = $user->getMaxRankForGroup($user->getGroup()); + if ($user->can('track_own_expenses')) + $options = array('status'=>ACTIVE,'max_rank'=>$rank,'include_self'=>true,'self_first'=>true); + else + $options = array('status'=>ACTIVE,'max_rank'=>$rank); + $user_list = $user->getUsers($options); + if (count($user_list) >= 1) { $form->addInput(array('type'=>'combobox', - 'onchange'=>'this.form.submit();', - 'name'=>'onBehalfUser', - 'style'=>'width: 250px;', - 'value'=>$on_behalf_id, + 'onchange'=>'this.form.user_changed.value=1;this.form.submit();', + 'name'=>'user', + 'value'=>$user_id, 'data'=>$user_list, 'datakeys'=>array('id','name'))); - $smarty->assign('on_behalf_control', 1); + $form->addInput(array('type'=>'hidden','name'=>'user_changed')); + $largeScreenCalendarRowSpan += 2; + $smarty->assign('user_dropdown', 1); } } // Dropdown for clients in MODE_TIME. Use all active clients. -if (MODE_TIME == $user->tracking_mode && $user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); +if (MODE_TIME == $tracking_mode && $user->isPluginEnabled('cl')) { + $active_clients = ttGroupHelper::getActiveClients(true); $form->addInput(array('type'=>'combobox', 'onchange'=>'fillProjectDropdown(this.value);', 'name'=>'client', - 'style'=>'width: 250px;', 'value'=>$cl_client, 'data'=>$active_clients, 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); // Note: in other modes the client list is filtered to relevant clients only. See below. + $largeScreenCalendarRowSpan += 2; } -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { +if ($show_project) { // Dropdown for projects assigned to user. $project_list = $user->getAssignedProjects(); $form->addInput(array('type'=>'combobox', - // 'onchange'=>'fillTaskDropdown(this.value);', 'name'=>'project', - 'style'=>'width: 250px;', 'value'=>$cl_project, 'data'=>$project_list, 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; // Dropdown for clients if the clients plugin is enabled. + $client_list = array(); if ($user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - // We need an array of assigned project ids to do some trimming. + $active_clients = ttGroupHelper::getActiveClients(true); + // We need an array of assigned project ids to do some trimming. foreach($project_list as $project) $projects_assigned_to_user[] = $project['id']; @@ -120,87 +154,100 @@ $form->addInput(array('type'=>'combobox', 'onchange'=>'fillProjectDropdown(this.value);', 'name'=>'client', - 'style'=>'width: 250px;', 'value'=>$cl_client, 'data'=>$client_list, 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; } } // If predefined expenses are configured, add controls to select an expense and quantity. -$predefined_expenses = ttTeamHelper::getPredefinedExpenses($user->team_id); +$predefined_expenses = ttGroupHelper::getPredefinedExpenses(); if ($predefined_expenses) { $form->addInput(array('type'=>'combobox', 'onchange'=>'recalculateCost();', 'name'=>'predefined_expense', - 'style'=>'width: 250px;', - 'value'=>$cl_predefined_expense, 'data'=>$predefined_expenses, 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - $form->addInput(array('type'=>'text','onchange'=>'recalculateCost();','maxlength'=>'40','name'=>'quantity','style'=>'width: 100px;','value'=>$cl_quantity)); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; + $form->addInput(array('type'=>'text','onchange'=>'recalculateCost();','maxlength'=>'40','name'=>'quantity')); + $largeScreenCalendarRowSpan += 2; +} +$form->addInput(array('type'=>'textarea','maxlength'=>'800','name'=>'item_name','value'=>$cl_item_name)); +$largeScreenCalendarRowSpan += 2; +$form->addInput(array('type'=>'text','maxlength'=>'40','name'=>'cost','value'=>$cl_cost)); +$largeScreenCalendarRowSpan += 2; +if ($showFiles) { + $form->addInput(array('type'=>'upload','name'=>'newfile','value'=>$i18n->get('button.submit'))); + $largeScreenCalendarRowSpan += 2; } -$form->addInput(array('type'=>'textarea','maxlength'=>'800','name'=>'item_name','style'=>'width: 250px; height:'.NOTE_INPUT_HEIGHT.'px;','value'=>$cl_item_name)); -$form->addInput(array('type'=>'text','maxlength'=>'40','name'=>'cost','style'=>'width: 100px;','value'=>$cl_cost)); $form->addInput(array('type'=>'calendar','name'=>'date','highlight'=>'expenses','value'=>$cl_date)); // calendar $form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_submit click. -$form->addInput(array('type'=>'submit','name'=>'btn_submit','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.submit'))); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','onclick'=>'browser_today.value=get_date()','value'=>$i18n->get('button.submit'))); // Submit. if ($request->isPost()) { if ($request->getParameter('btn_submit')) { + // Use the "limit" plugin if we have one. Ignore include errors. + // The "limit" plugin is not required for normal operation of Time Tracker. + @include('plugins/limit/access_check.php'); + // Validate user input. - if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client) - $err->add($i18n->getKey('error.client')); - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $err->add($i18n->getKey('error.project')); - } - if (!ttValidString($cl_item_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.item')); - if (!ttValidFloat($cl_cost)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.cost')); + if ($user->isPluginEnabled('cl') && $user->isOptionEnabled('client_required') && !$cl_client) + $err->add($i18n->get('error.client')); + if ($show_project && !$cl_project) + $err->add($i18n->get('error.project')); + if (!ttValidString($cl_item_name)) $err->add($i18n->get('error.field'), $i18n->get('label.comment')); + if (!ttValidFloat($cl_cost)) $err->add($i18n->get('error.field'), $i18n->get('label.cost')); // Prohibit creating entries in future. - if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($selected_date->after($browser_today)) - $err->add($i18n->getKey('error.future_date')); + if (!$user->isOptionEnabled('future_entries')) { + $browser_today = new ttDate($request->getParameter('browser_today', null)); + $server_tomorrow = new ttDate(); + $server_tomorrow->incrementDay(); + if ($selected_date->after($browser_today) || $selected_date->after($server_tomorrow)) + $err->add($i18n->get('error.future_date')); } // Finished validating input data. // Prohibit creating entries in locked range. if ($user->isDateLocked($selected_date)) - $err->add($i18n->getKey('error.range_locked')); + $err->add($i18n->get('error.range_locked')); // Insert record. if ($err->no()) { - if (ttExpenseHelper::insert(array('date'=>$cl_date,'user_id'=>$user->getActiveUser(), - 'client_id'=>$cl_client,'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost,'status'=>1))) { + $id = ttExpenseHelper::insert(array('date'=>$cl_date,'client_id'=>$cl_client, + 'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost,'status'=>1)); + + // Put a new file in storage if we have it. + if ($id && $showFiles && $_FILES['newfile']['name']) { + $fileHelper = new ttFileHelper($err); + $fields = array('entity_type'=>'expense', + 'entity_id' => $id, + 'file_name' => $_FILES['newfile']['name']); + $fileHelper->putFile($fields); + } + + if ($id) { header('Location: expenses.php'); exit(); } else - $err->add($i18n->getKey('error.db')); - } - } elseif ($request->getParameter('onBehalfUser')) { - if($user->canManageTeam()) { - unset($_SESSION['behalf_id']); - unset($_SESSION['behalf_name']); - - if($on_behalf_id != $user->id) { - $_SESSION['behalf_id'] = $on_behalf_id; - $_SESSION['behalf_name'] = ttUserHelper::getUserName($on_behalf_id); - } - header('Location: expenses.php'); - exit(); + $err->add($i18n->get('error.db')); } } } -$smarty->assign('day_total', ttExpenseHelper::getTotalForDay($user->getActiveUser(), $cl_date)); -$smarty->assign('expense_items', ttExpenseHelper::getItems($user->getActiveUser(), $cl_date)); +$smarty->assign('large_screen_calendar_row_span', $largeScreenCalendarRowSpan); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('show_project', $show_project); +$smarty->assign('show_files', $showFiles); +$smarty->assign('day_total', ttExpenseHelper::getTotalForDay($cl_date)); +$smarty->assign('expense_items', ttExpenseHelper::getItems($cl_date, $showFiles)); $smarty->assign('predefined_expenses', $predefined_expenses); $smarty->assign('client_list', $client_list); $smarty->assign('project_list', $project_list); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('timestring', $selected_date->toString($user->date_format)); -$smarty->assign('title', $i18n->getKey('title.expenses')); +$smarty->assign('timestring', $selected_date->toString($user->getDateFormat())); +$smarty->assign('title', $i18n->get('title.expenses')); $smarty->assign('content_page_name', 'expenses.tpl'); $smarty->display('index.tpl'); diff --git a/export.php b/export.php index f0469497e..2bcba2dce 100644 --- a/export.php +++ b/export.php @@ -1,53 +1,29 @@ getParameter('compression'); -$compressors = array('' => $i18n->getKey('form.export.compression_none')); +$compressors = array('' => $i18n->get('form.export.compression_none')); if (function_exists('bzcompress')) - $compressors['bzip'] = $i18n->getKey('form.export.compression_bzip'); + $compressors['bzip'] = $i18n->get('form.export.compression_bzip'); $form = new Form('exportForm'); $form->addInput(array('type'=>'combobox','name'=>'compression','value'=>$cl_compression,'data'=>$compressors)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.export'))); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.export'))); if ($request->isPost()) { - $filename = 'team_data.xml'; + $filename = 'group_data.xml'; $mime_type = 'text/xml'; $compress = false; if ('bzip' == $cl_compression) { @@ -56,7 +32,7 @@ $mime_type = 'application/x-bzip2'; } - $exportHelper = new ttExportHelper(); + $exportHelper = new ttOrgExportHelper(); if ($exportHelper->createDataFile($compress)) { header('Pragma: public'); // This is needed for IE8 to download files over https. header('Content-Type: '.$mime_type); @@ -74,10 +50,10 @@ } exit; } else - $err->add($i18n->getKey('error.sys')); + $err->add($i18n->get('error.sys')); } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.export')); +$smarty->assign('title', $i18n->get('title.export')); $smarty->assign('content_page_name', 'export.tpl'); $smarty->display('index.tpl'); diff --git a/feature_disabled.php b/feature_disabled.php new file mode 100644 index 000000000..d15413541 --- /dev/null +++ b/feature_disabled.php @@ -0,0 +1,12 @@ +add($i18n->get('error.feature_disabled')); +if ($auth->isAuthenticated()) $smarty->assign('authenticated', true); // Used in header.tpl for menu display. + +$smarty->assign('title', $i18n->get('label.error')); +$smarty->assign('content_page_name', 'access_denied.tpl'); +$smarty->display('index.tpl'); diff --git a/file_delete.php b/file_delete.php new file mode 100644 index 000000000..e6cb53378 --- /dev/null +++ b/file_delete.php @@ -0,0 +1,102 @@ +getParameter('id'); +$file = ttFileHelper::get($cl_file_id); +if (!$file) { + header('Location: access_denied.php'); + exit(); +} +// Entity-specific checks. +$entity_type = $file['entity_type']; +if ($entity_type == 'time') { + if (!(ttAccessAllowed('track_own_time') || ttAccessAllowed('track_time')) || !ttTimeHelper::getRecord($file['entity_id'])) { + header('Location: access_denied.php'); + exit(); + } +} +if ($entity_type == 'expense') { + if (!(ttAccessAllowed('track_own_expenses') || ttAccessAllowed('track_expenses')) || !ttExpenseHelper::getItemForFileView($file['entity_id'])) { + header('Location: access_denied.php'); + exit(); + } +} +if ($entity_type == 'timesheet') { + if (!(ttAccessAllowed('track_own_time') || ttAccessAllowed('track_time')) || !ttTimesheetHelper::getTimesheet($file['entity_id'])) { + header('Location: access_denied.php'); + exit(); + } +} +if ($entity_type == 'project') { + if (!ttAccessAllowed('manage_projects') || !ttProjectHelper::get($file['entity_id'])) { + header('Location: access_denied.php'); + exit(); + } +} +if (!($entity_type == 'time' || $entity_type != 'expense' || $entity_type != 'timesheet' || $entity_type == 'project')) { + // Currently, files are only associated with time records, expense items, timesheets, and projects. + // Improve access checks when the feature evolves. + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$file_to_delete = $file['file_name']; + +$form = new Form('fileDeleteForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_file_id)); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); + +if ($request->isPost()) { + if ($request->getParameter('btn_delete')) { + $fileHelper = new ttFileHelper($err); + $deleted = $fileHelper->deleteFile($file); + if ($deleted) { + if ($entity_type == 'time') { + header('Location: time_files.php?id='.$file['entity_id']); + } + if ($entity_type == 'expense') { + header('Location: expense_files.php?id='.$file['entity_id']); + } + if ($entity_type == 'timesheet') { + header('Location: timesheet_files.php?id='.$file['entity_id']); + } + if ($entity_type == 'project') { + header('Location: project_files.php?id='.$file['entity_id']); + } + exit(); + } + } elseif ($request->getParameter('btn_cancel')) { + if ($entity_type == 'time') { + header('Location: time_files.php?id='.$file['entity_id']); + } + if ($entity_type == 'expense') { + header('Location: expense_files.php?id='.$file['entity_id']); + } + if ($entity_type == 'timesheet') { + header('Location: timesheet_files.php?id='.$file['entity_id']); + } + if ($entity_type == 'project') { + header('Location: project_files.php?id='.$file['entity_id']); + } + exit(); + } +} // isPost + +$smarty->assign('file_to_delete', $file_to_delete); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.fileDeleteForm.btn_cancel.focus()"'); +$smarty->assign('title', $i18n->get('title.delete_file')); +$smarty->assign('content_page_name', 'file_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/file_download.php b/file_download.php new file mode 100644 index 000000000..2fb52f79b --- /dev/null +++ b/file_download.php @@ -0,0 +1,63 @@ +getParameter('id'); +$file = ttFileHelper::get($cl_file_id); +if (!$file) { + header('Location: access_denied.php'); + exit(); +} +// Entity-specific checks. +$entity_type = $file['entity_type']; +if ($entity_type == 'time') { + if (!(ttAccessAllowed('track_own_time') || ttAccessAllowed('track_time')) || !ttTimeHelper::getRecordForFileView($file['entity_id'])) { + header('Location: access_denied.php'); + exit(); + } +} +if ($entity_type == 'expense') { + if (!(ttAccessAllowed('track_own_expenses') || ttAccessAllowed('track_expenses')) || !ttExpenseHelper::getItemForFileView($file['entity_id'])) { + header('Location: access_denied.php'); + exit(); + } +} +if ($entity_type == 'project') { + if (!(ttAccessAllowed('view_own_projects') || ttAccessAllowed('manage_projects')) || !ttProjectHelper::get($file['entity_id'])) { + header('Location: access_denied.php'); + exit(); + } +} +if ($entity_type != 'project' && $entity_type != 'time' && $entity_type != 'expense') { + // Currently, files are only associated with time records, expense items, and projects. + // Improve access checks when the feature evolves. + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$fileHelper = new ttFileHelper($err); + +if ($fileHelper->getFile($file)) { + header('Pragma: public'); // This is needed for IE8 to download files over https. + header('Content-Type: application/octet-stream'); + header('Expires: '.gmdate('D, d M Y H:i:s').' GMT'); + header('Content-Disposition: attachment; filename="'.$file['file_name'].'"'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Cache-Control: private', false); + + echo $fileHelper->getFileData(); + exit; +} + +$smarty->assign('title', $i18n->get('title.download_file')); +$smarty->assign('content_page_name', 'file_download.tpl'); +$smarty->display('index.tpl'); diff --git a/file_edit.php b/file_edit.php new file mode 100644 index 000000000..aeb8f70c7 --- /dev/null +++ b/file_edit.php @@ -0,0 +1,100 @@ +getParameter('id'); +$file = ttFileHelper::get($cl_file_id); +if (!$file) { + header('Location: access_denied.php'); + exit(); +} +// Entity-specific checks. +$entity_type = $file['entity_type']; +if ($entity_type == 'time') { + if (!(ttAccessAllowed('track_own_time') || ttAccessAllowed('track_time')) || !ttTimeHelper::getRecord($file['entity_id'])) { + header('Location: access_denied.php'); + exit(); + } +} +if ($entity_type == 'expense') { + if (!(ttAccessAllowed('track_own_expenses') || ttAccessAllowed('track_expenses')) || !ttExpenseHelper::getItemForFileView($file['entity_id'])) { + header('Location: access_denied.php'); + exit(); + } +} +if ($entity_type == 'timesheet') { + if (!(ttAccessAllowed('track_own_time') || ttAccessAllowed('track_time')) || !ttTimesheetHelper::getTimesheet($file['entity_id'])) { + header('Location: access_denied.php'); + exit(); + } +} +if ($entity_type == 'project') { + if (!ttAccessAllowed('manage_projects') || !ttProjectHelper::get($file['entity_id'])) { + header('Location: access_denied.php'); + exit(); + } +} +if (!($entity_type == 'time' || $entity_type != 'expense' || $entity_type != 'timesheet' || $entity_type == 'project')) { + // Currently, files are only associated with time records, expense items, timesheets, and projects. + // Improve access checks when the feature evolves. + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +if ($request->isPost()) { + $cl_description = trim($request->getParameter('description')); +} else { + $cl_description = $file['description']; +} +$cl_name = $file['file_name']; + +$form = new Form('fileForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_file_id)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'file_name','value'=>$cl_name)); +$form->getElement('file_name')->setEnabled(false); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); + +if ($request->isPost()) { + // Validate user input. + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + + if ($err->no()) { + if ($request->getParameter('btn_save')) { + // Update file information. + $updated = ttFileHelper::update(array('id' => $cl_file_id,'description' => $cl_description)); + if ($updated) { + if ($entity_type == 'time') { + header('Location: time_files.php?id='.$file['entity_id']); + } + if ($entity_type == 'expense') { + header('Location: expense_files.php?id='.$file['entity_id']); + } + if ($entity_type == 'timesheet') { + header('Location: timesheet_files.php?id='.$file['entity_id']); + } + if ($entity_type == 'project') { + header('Location: project_files.php?id='.$file['entity_id']); + } + exit(); + } else + $err->add($i18n->get('error.db')); + } + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.fileForm.description.focus()"'); +$smarty->assign('title', $i18n->get('title.edit_file')); +$smarty->assign('content_page_name', 'file_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/group_add.php b/group_add.php new file mode 100644 index 000000000..6f88af205 --- /dev/null +++ b/group_add.php @@ -0,0 +1,50 @@ +isPost()) { + $cl_name = trim($request->getParameter('group_name')); + $cl_description = trim($request->getParameter('description')); +} + +$form = new Form('groupForm'); +$form->addInput(array('type'=>'text','maxlength'=>'200','name'=>'group_name','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); + +if ($request->isPost()) { + // Validate user input. + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + + if ($err->no()) { + if (!ttGroupHelper::getSubgroupByName($cl_name)) { + if (ttGroupHelper::insertSubgroup(array( + 'name' => $cl_name, + 'description' => $cl_description))) { + header('Location: groups.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } else + $err->add($i18n->get('error.object_exists')); + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.groupForm.group_name.focus()"'); +$smarty->assign('title', $i18n->get('title.add_group')); +$smarty->assign('content_page_name', 'group_add.tpl'); +$smarty->display('index.tpl'); diff --git a/group_advanced_edit.php b/group_advanced_edit.php new file mode 100644 index 000000000..26134c789 --- /dev/null +++ b/group_advanced_edit.php @@ -0,0 +1,80 @@ +getGroup()); +$config = $user->getConfigHelper(); + +if ($request->isPost()) { + $cl_group = trim($request->getParameter('group_name')); + $cl_description = trim($request->getParameter('description')); + $cl_bcc_email = trim($request->getParameter('bcc_email')); + $cl_allow_ip = trim($request->getParameter('allow_ip')); + $cl_password_complexity = trim($request->getParameter('password_complexity')); + $cl_2fa = (bool) $request->getParameter('2fa'); +} else { + $cl_group = $group['name']; + $cl_description = $group['description']; + $cl_bcc_email = $group['bcc_email']; + $cl_allow_ip = $group['allow_ip']; + $cl_password_complexity = $group['password_complexity']; + $cl_2fa = $config->getDefinedValue('2fa'); +} + +$form = new Form('groupAdvancedForm'); +$form->addInput(array('type'=>'text','maxlength'=>'200','name'=>'group_name','value'=>$cl_group)); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'bcc_email','value'=>$cl_bcc_email)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'allow_ip','value'=>$cl_allow_ip)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'password_complexity','value'=>$cl_password_complexity)); +$form->addInput(array('type'=>'checkbox','name'=>'2fa','value'=>$cl_2fa)); + +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); + +if ($request->isPost()) { + if ($request->getParameter('btn_save')) { + // Validate user input. + if (!ttValidString($cl_group)) $err->add($i18n->get('error.field'), $i18n->get('label.group_name')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + if (!ttValidEmail($cl_bcc_email, true)) $err->add($i18n->get('error.field'), $i18n->get('label.bcc')); + if (!ttValidIP($cl_allow_ip, true)) $err->add($i18n->get('error.field'), $i18n->get('form.group_advanced_edit.allow_ip')); + if (!ttValidPasswordComplexity($cl_password_complexity, true)) $err->add($i18n->get('error.field'), $i18n->get('form.group_advanced_edit.password_complexity')); + // Finished validating user input. + + if ($err->no()) { + // Update config. + $config->setDefinedValue('2fa', $cl_2fa); + + // Update group. + if ($user->updateGroup(array( + 'name' => $cl_group, + 'description' => $cl_description, + 'bcc_email' => $cl_bcc_email, + 'allow_ip' => $cl_allow_ip, + 'password_complexity' => $cl_password_complexity, + 'config' => $config->getConfig()))) { + header('Location: success.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.edit_group')); +$smarty->assign('content_page_name', 'group_advanced_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/group_delete.php b/group_delete.php new file mode 100644 index 000000000..377bb80e9 --- /dev/null +++ b/group_delete.php @@ -0,0 +1,68 @@ +getParameter('id'); +if (!$user->isGroupValid($group_id)) { + header('Location: access_denied.php'); // Wrong group id. + exit(); +} +if ($group_id == $user->group_id && !$user->can('delete_group')) { + header('Location: access_denied.php'); // Trying to delete home group without right. + exit(); +} +// End of access checks. + +$group_name = ttGroupHelper::getGroupName($group_id); + +$form = new Form('groupForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$group_id)); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); + +if ($request->isPost()) { + if ($request->getParameter('btn_delete')) { + $markedDeleted = ttGroupHelper::markGroupDeleted($group_id); + if ($markedDeleted) { + if ($group_id == $user->group_id) { + // We marked deleted our own group. Logout and redirect to login page. + $auth->doLogout(); + session_unset(); + header('Location: login.php'); + exit(); + } else { + // We marked deleted a subgroup. + if ($user->behalfGroup && $user->behalfGroup->id == $group_id) + $user->setOnBehalfGroup($user->group_id); // Remove on behalf group from session. + header('Location: success.php'); + exit(); + } + } else + $err->add($i18n->get('error.db')); + } + + if ($request->getParameter('btn_cancel')) { + if ($group_id == $user->group_id) { + header('Location: group_edit.php'); + exit(); + } else { + header('Location: groups.php'); + exit(); + } + } +} // isPost + +$smarty->assign('group_to_delete', $group_name); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.delete_group')); +$smarty->assign('content_page_name', 'group_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/group_edit.php b/group_edit.php new file mode 100644 index 000000000..dbfca56bb --- /dev/null +++ b/group_edit.php @@ -0,0 +1,241 @@ +isGet()) { + $group_id = $request->getParameter('id') ? (int)$request->getParameter('id') : $user->getGroup(); +} else { + $group_id = $request->getParameter('group') ? (int)$request->getParameter('group') : $user->getGroup(); +} +$home_group = $user->group_id == $group_id; +if ($home_group) { + // Editing home group. + if (!ttAccessAllowed('manage_basic_settings')) { + header('Location: access_denied.php'); // Not allowed to edit home group settings. + exit(); + } +} else { + // Editing a subgroup. + if (!ttAccessAllowed('manage_subgroups')) { + header('Location: access_denied.php'); // No right to manage subgroups. + exit(); + } + if (!$user->isSubgroupValid($group_id)) { + header('Location: access_denied.php'); // Wrong subgroup. + exit(); + } +} +// End of access checks. + +// Set on behalf group accordingly. +$groupChanged = (bool)$request->getParameter('group_changed'); +if ($request->isPost() && $groupChanged) { + $user->setOnBehalfGroup($group_id); // User changed the group in a post using group selector on group_edit.php. +} +if ($request->isGet() && !$home_group) { + $user->setOnBehalfGroup($group_id); // User got here in a get by clicking an edit icon for a subgroup on groups.php. +} + +$groups = $user->getGroupsForDropdown(); +$group = ttGroupHelper::getGroupAttrs($group_id); +$config = $user->getConfigHelper(); + +if (!defined('CURRENCY_DEFAULT')) define('CURRENCY_DEFAULT', '$'); + +if ($request->isPost() && !$groupChanged) { + $cl_currency = trim($request->getParameter('currency')); + if (!$cl_currency) $cl_currency = CURRENCY_DEFAULT; + $cl_lang = $request->getParameter('lang'); + $cl_decimal_mark = $request->getParameter('decimal_mark'); + $cl_date_format = $request->getParameter('date_format'); + $cl_time_format = $request->getParameter('time_format'); + $cl_start_week = $request->getParameter('start_week'); + $cl_holidays = trim($request->getParameter('holidays')); + $cl_tracking_mode = $request->getParameter('tracking_mode'); + $cl_project_required = $request->getParameter('project_required'); + $cl_record_type = $request->getParameter('record_type'); + $cl_punch_mode = (bool)$request->getParameter('punch_mode'); + $cl_one_uncompleted = (bool)$request->getParameter('one_uncompleted'); + $cl_allow_overlap = (bool)$request->getParameter('allow_overlap'); + $cl_future_entries = (bool)$request->getParameter('future_entries'); + $cl_uncompleted_indicators = (bool)$request->getParameter('uncompleted_indicators'); + $cl_confirm_save = (bool)$request->getParameter('confirm_save'); +} else { + $cl_currency = ($group['currency'] == '' ? CURRENCY_DEFAULT : $group['currency']); + $cl_lang = $group['lang']; + $cl_decimal_mark = $group['decimal_mark']; + $cl_date_format = $group['date_format']; + $cl_time_format = $group['time_format']; + $cl_start_week = $group['week_start']; + $cl_holidays = $group['holidays']; + $cl_tracking_mode = $group['tracking_mode']; + $cl_project_required = $group['project_required']; + $cl_record_type = $group['record_type']; + $cl_punch_mode = $config->getDefinedValue('punch_mode'); + $cl_one_uncompleted = $config->getDefinedValue('one_uncompleted'); + $cl_allow_overlap = $config->getDefinedValue('allow_overlap'); + $cl_future_entries = $config->getDefinedValue('future_entries'); + $cl_uncompleted_indicators = $config->getDefinedValue('uncompleted_indicators'); + $cl_confirm_save = $config->getDefinedValue('confirm_save'); +} + +$form = new Form('groupForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$group_id)); +if (count($groups) > 1) { + $form->addInput(array('type'=>'combobox', + 'onchange'=>'document.groupForm.group_changed.value=1;document.groupForm.submit();', + 'name'=>'group', + 'value'=>$group_id, + 'data'=>$groups, + 'datakeys'=>array('id','name'))); + $form->addInput(array('type'=>'hidden','name'=>'group_changed')); + $smarty->assign('group_dropdown', 1); +} +$form->addInput(array('type'=>'text','maxlength'=>'7','name'=>'currency','value'=>$cl_currency)); + +// Prepare an array of available languages. +$lang_files = I18n::getLangFileList(); +foreach ($lang_files as $lfile) { + $content = file(RESOURCE_DIR."/".$lfile); + $lname = ''; + foreach ($content as $line) { + if (strstr($line, 'i18n_language')) { + $a = explode('=', $line); + $lname = trim(str_replace(';','',str_replace("'","",$a[1]))); + break; + } + } + unset($content); + $longname_lang[] = array('id'=>I18n::getLangFromFilename($lfile),'name'=>$lname); +} +$longname_lang = mu_sort($longname_lang, 'name'); +$form->addInput(array('type'=>'combobox','name'=>'lang','data'=>$longname_lang,'datakeys'=>array('id','name'),'value'=>$cl_lang)); + +$DECIMAL_MARK_OPTIONS = array(array('id'=>'.','name'=>'.'),array('id'=>',','name'=>',')); +$form->addInput(array('type'=>'combobox','name'=>'decimal_mark','class'=>'dropdown-field-with-format-example','data'=>$DECIMAL_MARK_OPTIONS,'datakeys'=>array('id','name'),'value'=>$cl_decimal_mark, + 'onchange'=>'adjustDecimalPreview()')); + +$DATE_FORMAT_OPTIONS = array( + array('id'=>'%Y-%m-%d','name'=>'Y-m-d'), + array('id'=>'%m/%d/%Y','name'=>'m/d/Y'), + array('id'=>'%d-%m-%Y','name'=>'d-m-Y'), + array('id'=>'%d.%m.%Y','name'=>'d.m.Y'), + array('id'=>'%d.%m.%Y %a','name'=>'d.m.Y a')); +$form->addInput(array('type'=>'combobox','name'=>'date_format','class'=>'dropdown-field-with-format-example','data'=>$DATE_FORMAT_OPTIONS,'datakeys'=>array('id','name'),'value'=>$cl_date_format, + 'onchange'=>'MakeFormatPreview("date_format_preview", this);')); +$TIME_FORMAT_OPTIONS = array( + array('id'=>'%H:%M','name'=>$i18n->get('form.group_edit.24_hours')), + array('id'=>'%I:%M %p','name'=>$i18n->get('form.group_edit.12_hours'))); +$form->addInput(array('type'=>'combobox','name'=>'time_format','class'=>'dropdown-field-with-format-example','data'=>$TIME_FORMAT_OPTIONS,'datakeys'=>array('id','name'),'value'=>$cl_time_format, + 'onchange'=>'MakeFormatPreview("time_format_preview", this);')); + +// Prepare week start choices. +$week_start_options = array(); +foreach ($i18n->weekdayNames as $id => $week_dn) { + $week_start_options[] = array('id' => $id, 'name' => $week_dn); +} +$form->addInput(array('type'=>'combobox','name'=>'start_week','data'=>$week_start_options,'datakeys'=>array('id','name'),'value'=>$cl_start_week)); + +// Show holidays control. +$form->addInput(array('type'=>'text','name'=>'holidays','value'=>$cl_holidays)); + +// Prepare tracking mode choices. +$tracking_mode_options = array(); +$tracking_mode_options[MODE_TIME] = $i18n->get('form.group_edit.mode_time'); +$tracking_mode_options[MODE_PROJECTS] = $i18n->get('form.group_edit.mode_projects'); +$tracking_mode_options[MODE_PROJECTS_AND_TASKS] = $i18n->get('form.group_edit.mode_projects_and_tasks'); +$form->addInput(array('type'=>'combobox','name'=>'tracking_mode','data'=>$tracking_mode_options,'value'=>$cl_tracking_mode)); +$form->addInput(array('type'=>'checkbox','name'=>'project_required','value'=>$cl_project_required)); + +// Prepare record type choices. +$record_type_options = array(); +$record_type_options[TYPE_ALL] = $i18n->get('form.group_edit.type_all'); +$record_type_options[TYPE_START_FINISH] = $i18n->get('form.group_edit.type_start_finish'); +$record_type_options[TYPE_DURATION] = $i18n->get('form.group_edit.type_duration'); +$form->addInput(array('type'=>'combobox','name'=>'record_type','data'=>$record_type_options,'value'=>$cl_record_type)); + +// Punch mode checkbox. +$form->addInput(array('type'=>'checkbox','name'=>'punch_mode','value'=>$cl_punch_mode)); + +// One uncompleted. +$form->addInput(array('type'=>'checkbox','name'=>'one_uncompleted','value'=>$cl_one_uncompleted)); + +// Allow overlap checkbox. +$form->addInput(array('type'=>'checkbox','name'=>'allow_overlap','value'=>$cl_allow_overlap)); + +// Future entries checkbox. +$form->addInput(array('type'=>'checkbox','name'=>'future_entries','value'=>$cl_future_entries)); + +// Uncompleted indicators checkbox. +$form->addInput(array('type'=>'checkbox','name'=>'uncompleted_indicators','value'=>$cl_uncompleted_indicators)); + +// Confirm save checkbox. +$form->addInput(array('type'=>'checkbox','name'=>'confirm_save','value'=>$cl_confirm_save)); + +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); +if ($user->can('delete_group')) $form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('button.delete'))); + +$form->setValueByElement('group_changed',''); + +if ($request->isPost()) { + + if ($request->getParameter('btn_delete')) { + // Delete button pressed, redirect. + header('Location: group_delete.php?id='.$group_id); + exit(); + } + + if ($request->getParameter('btn_save')) { + // Validate user input. + if (!ttValidString($cl_currency, true)) $err->add($i18n->get('error.field'), $i18n->get('label.currency')); + if (!ttValidHolidays($cl_holidays)) $err->add($i18n->get('error.field'), $i18n->get('form.group_edit.holidays')); + // Finished validating user input. + + if ($err->no()) { + // Update config. + $config->setDefinedValue('punch_mode', $cl_punch_mode); + $config->setDefinedValue('one_uncompleted', $cl_one_uncompleted); + $config->setDefinedValue('allow_overlap', $cl_allow_overlap); + $config->setDefinedValue('future_entries', $cl_future_entries); + $config->setDefinedValue('uncompleted_indicators', $cl_uncompleted_indicators); + $config->setDefinedValue('confirm_save', $cl_confirm_save); + + if ($user->updateGroup(array( + 'group_id' => $group_id, + 'currency' => $cl_currency, + 'lang' => $cl_lang, + 'decimal_mark' => $cl_decimal_mark, + 'date_format' => $cl_date_format, + 'time_format' => $cl_time_format, + 'week_start' => $cl_start_week, + 'holidays' => $cl_holidays, + 'tracking_mode' => $cl_tracking_mode, + 'project_required' => $cl_project_required, + 'record_type' => $cl_record_type, + 'uncompleted_indicators' => $cl_uncompleted_indicators, + 'config' => $config->getConfig()))) { + header('Location: success.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } + } +} // isPost + +$smarty->assign('group_dropdown', count($groups) > 1); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="handlePluginCheckboxes();"'); +$smarty->assign('title', $i18n->get('title.edit_group')); +$smarty->assign('content_page_name', 'group_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/groups.php b/groups.php new file mode 100644 index 000000000..ca01a5f7c --- /dev/null +++ b/groups.php @@ -0,0 +1,50 @@ +isPost()) { + $group_id = $request->getParameter('group'); + if (!ttValidInteger($group_id)) { + header('Location: access_denied.php'); // Protection against sql injection. + exit(); + } + if (!$user->isGroupValid($group_id)) { + header('Location: access_denied.php'); // Wrong group id in post. + exit(); + } +} +// End of access checks. + +if ($request->isPost()) { + $group_id = (int)$request->getParameter('group'); + $user->setOnBehalfGroup($group_id); +} else { + $group_id = $user->getGroup(); +} + +$form = new Form('subgroupsForm'); +$groups = $user->getGroupsForDropdown(); +if (count($groups) > 1) { + $form->addInput(array('type'=>'combobox', + 'onchange'=>'this.form.submit();', + 'name'=>'group', + 'value'=>$group_id, + 'data'=>$groups, + 'datakeys'=>array('id','name'))); + $smarty->assign('group_dropdown', 1); +} + +$smarty->assign('subgroups', $user->getSubgroups($group_id)); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('label.subgroups')); +$smarty->assign('content_page_name', 'groups.tpl'); +$smarty->display('index.tpl'); diff --git a/images/1x1.gif b/img/1x1.gif similarity index 100% rename from images/1x1.gif rename to img/1x1.gif diff --git a/images/calendar.gif b/img/calendar.gif similarity index 100% rename from images/calendar.gif rename to img/calendar.gif diff --git a/img/icon-delete.png b/img/icon-delete.png new file mode 100644 index 000000000..046a7e3d6 Binary files /dev/null and b/img/icon-delete.png differ diff --git a/img/icon-edit.png b/img/icon-edit.png new file mode 100644 index 000000000..83f837166 Binary files /dev/null and b/img/icon-edit.png differ diff --git a/img/icon-file.png b/img/icon-file.png new file mode 100644 index 000000000..264ab6eb9 Binary files /dev/null and b/img/icon-file.png differ diff --git a/img/icon-files.png b/img/icon-files.png new file mode 100644 index 000000000..2ee53ec5c Binary files /dev/null and b/img/icon-files.png differ diff --git a/img/icon-now.png b/img/icon-now.png new file mode 100644 index 000000000..44cd60462 Binary files /dev/null and b/img/icon-now.png differ diff --git a/img/icon-question-mark.png b/img/icon-question-mark.png new file mode 100644 index 000000000..a6225d271 Binary files /dev/null and b/img/icon-question-mark.png differ diff --git a/images/tt_logo.png b/img/logo.png similarity index 100% rename from images/tt_logo.png rename to img/logo.png diff --git a/images/subm_bg.gif b/img/subm_bg.gif similarity index 100% rename from images/subm_bg.gif rename to img/subm_bg.gif diff --git a/images/top_bg.gif b/img/top_bg.gif similarity index 100% rename from images/top_bg.gif rename to img/top_bg.gif diff --git a/import.php b/import.php index ce6aa7230..b743b3f5a 100644 --- a/import.php +++ b/import.php @@ -1,37 +1,13 @@ addInput(array('type'=>'upload','name'=>'xmlfile','value'=>'browse','maxsize'=>67108864)); // 64 MB file upload limit. // Note: for the above limit to work make sure to set upload_max_filesize and post_max_size in php.ini to at least 64M. -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.import'))); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.import'))); if ($request->isPost()) { - - $import = new ttImportHelper($err); - $import->importXml(); - if ($err->no()) $msg->add($i18n->getKey('form.import.success')); + $importHelper = new ttOrgImportHelper($err); + $importHelper->importXml(); + if ($err->no()) $msg->add($i18n->get('form.import.success')); } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray()) ); -$smarty->assign('title', $i18n->getKey('title.import')); +$smarty->assign('title', $i18n->get('title.import')); $smarty->assign('content_page_name', 'import.tpl'); $smarty->display('index.tpl'); diff --git a/index.php b/index.php index 51e1e016f..c74f2688b 100644 --- a/index.php +++ b/index.php @@ -1,66 +1,20 @@ isAuthenticated()) { - if ($user->isAdmin()) { - header('Location: admin_teams.php'); + if ($user->can('administer_site')) { + header('Location: admin_groups.php'); exit(); } elseif ($user->isClient()) { header('Location: reports.php'); exit(); } } - -// Redirect to time.php or mobile/time.php for other roles. - -// Regexp to determine mobile browser was taken from detectmobilebrowsers.com on Aug 24, 2012. -$useragent = $_SERVER['HTTP_USER_AGENT']; -$mobileBrowser = preg_match('/android.+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|meego.+mobile|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i',$useragent)||preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i',substr($useragent,0,4)); - -if ($mobileBrowser) { -?> - - - - - - - - @@ -69,11 +23,6 @@ location.href = "time.php?date="+(new Date()).strftime(''); - - diff --git a/initialize.php b/initialize.php index 1a508e943..18946798c 100644 --- a/initialize.php +++ b/initialize.php @@ -1,47 +1,33 @@ =')) { + if (function_exists('mysqli_report')) + mysqli_report(MYSQLI_REPORT_OFF); + else + die("mysqli_report function is not available."); // No point to continue as mysqli will not work. +} + +define("APP_VERSION", "1.22.22.5818"); define("APP_DIR", dirname(__FILE__)); define("LIBRARY_DIR", APP_DIR."/WEB-INF/lib"); define("TEMPLATE_DIR", APP_DIR."/WEB-INF/templates"); // Date format for database and URI parameters. define('DB_DATEFORMAT', '%Y-%m-%d'); +define('MAX_RANK', 512); // Max user rank. require_once(LIBRARY_DIR.'/common.lib.php'); @@ -74,7 +60,6 @@ $smarty->use_sub_dirs = false; $smarty->template_dir = TEMPLATE_DIR; $smarty->compile_dir = TEMPLATE_DIR.'_c'; -$GLOBALS['SMARTY'] = &$smarty; // Note: these 3 settings below used to be in .htaccess file. Moved them here to eliminate "error 500" problems // with some shared hostings that do not have AllowOverride Options or AllowOverride All in their apache configurations. @@ -84,16 +69,26 @@ $phpsessid_ttl = defined('PHPSESSID_TTL') ? PHPSESSID_TTL : 60*60*24; // Set lifetime for garbage collection. ini_set('session.gc_maxlifetime', $phpsessid_ttl); +// Set PHP session path, if defined to avoid garbage collection interference from other scripts. +if (defined('PHP_SESSION_PATH') && realpath(PHP_SESSION_PATH)) { + ini_set('session.save_path', realpath(PHP_SESSION_PATH)); + ini_set('session.gc_probability', 1); +} + +// "tt_" prefix is to avoid sharing session with other PHP apps that do not name session. +if (!defined('SESSION_COOKIE_NAME')) define('SESSION_COOKIE_NAME', 'tt_PHPSESSID'); +if (!defined('LOGIN_COOKIE_NAME')) define('LOGIN_COOKIE_NAME', 'tt_login'); + // Set session cookie lifetime. session_set_cookie_params($phpsessid_ttl); -if (isset($_COOKIE['tt_PHPSESSID'])) { +if (isset($_COOKIE[SESSION_COOKIE_NAME])) { // Extend PHP session cookie lifetime by PHPSESSID_TTL (if defined, otherwise 24 hours) // so that users don't have to re-login during this period from now. - setcookie('tt_PHPSESSID', $_COOKIE['tt_PHPSESSID'], time() + $phpsessid_ttl, '/'); + setcookie(SESSION_COOKIE_NAME, $_COOKIE[SESSION_COOKIE_NAME], time() + $phpsessid_ttl, '/'); } // Start or resume PHP session. -session_name('tt_PHPSESSID'); // "tt_" prefix is to avoid sharing session with other PHP apps that do not name session. +session_name(SESSION_COOKIE_NAME); @session_start(); // Authorization. @@ -120,36 +115,14 @@ define('TYPE_START_FINISH', 1); // Time record has start and finish times. define('TYPE_DURATION', 2); // Time record has only duration, no start and finish times. -// Definitions for uncompleted time entry indicators. -define('UNCOMPLETED_INDICATORS_NONE', 0); // Do not show indicators. -define('UNCOMPLETED_INDICATORS', 1); // Show indicators. - -// User access rights - bits that collectively define an access mask to the system (a role). -// We'll have some bits here (1,2, etc...) reserved for future use. -define('right_data_entry', 4); // Right to enter work hours and expenses. -define('right_view_charts', 8); // Right to view charts. -define('right_view_reports', 16); // Right to view reports. -define('right_view_invoices', 32); // Right to view invoices. -define('right_manage_team', 64); // Right to manage team. Note that this is not full access to team. -define('right_assign_roles', 128); // Right to assign user roles. -define('right_export_team', 256); // Right to export team data to a file. -define('right_administer_site', 1024); // Admin account right to manage the application as a whole. - -// User roles. -define('ROLE_USER', 4); // Regular user. -define('ROLE_CLIENT', 16); // Client (to view reports and invoices). -define('ROLE_COMANAGER', 68); // Team co-manager. Can do many things but not as much as team manager. -define('ROLE_MANAGER', 324); // Team manager. Can do everything for a team. -define('ROLE_SITE_ADMIN', 1024); // Site administrator. - - define('CHARSET', 'utf-8'); -date_default_timezone_set(@date_default_timezone_get()); +// Definitions of max counts of utf8mb4 characters for various varchar database fields. +define('MAX_NAME_CHARS', 80); +define('MAX_DESCR_CHARS', 255); +define('MAX_CURRENCY_CHARS', 7); -// Strip auto-inserted extra slashes when magic_quotes ON for PHP versions prior to 5.4.0. -if (get_magic_quotes_gpc()) - magic_quotes_off(); +date_default_timezone_set(@date_default_timezone_get()); // Initialize global objects that are needed for the application. import('html.HttpRequest'); @@ -163,8 +136,8 @@ import('ttUser'); $user = new ttUser(null, $auth->getUserId()); if ($user->custom_logo) { - $smarty->assign('custom_logo', 'images/'.$user->team_id.'.png'); - $smarty->assign('mobile_custom_logo', '../images/'.$user->team_id.'.png'); + $smarty->assign('custom_logo', 'img/'.$user->group_id.'.png'); + $smarty->assign('mobile_custom_logo', '../img/'.$user->group_id.'.png'); } $smarty->assign('user', $user); @@ -191,9 +164,6 @@ // Load i18n file. $i18n->load($lang); -$GLOBALS['I18N'] = &$i18n; - -$GLOBALS['USER'] = &$user; // Assign things for smarty to use in template files. $smarty->assign('i18n', $i18n->keys); diff --git a/invoice_add.php b/invoice_add.php index 0e752dca3..e7018f009 100644 --- a/invoice_add.php +++ b/invoice_add.php @@ -1,45 +1,28 @@ isPluginEnabled('iv')) { +// Access checks. +if (!ttAccessAllowed('manage_invoices')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('iv')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. +$cl_date = $cl_client = $cl_project = $cl_number = $cl_start = $cl_finish = null; if ($request->isPost()) { $cl_date = $request->getParameter('date'); - $cl_client = $request->getParameter('client'); + $cl_client = (int)$request->getParameter('client'); $cl_project = $request->getParameter('project'); $cl_number = trim($request->getParameter('number')); $cl_start = $request->getParameter('start'); @@ -51,34 +34,35 @@ // Dropdown for clients if the clients plugin is enabled. if ($user->isPluginEnabled('cl')) { - $clients = ttTeamHelper::getActiveClients($user->team_id); - $form->addInput(array('type'=>'combobox','name'=>'client','style'=>'width: 250px;','data'=>$clients,'datakeys'=>array('id','name'),'value'=>$cl_client,'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + $clients = ttGroupHelper::getActiveClients(); + $form->addInput(array('type'=>'combobox','name'=>'client','data'=>$clients,'datakeys'=>array('id','name'),'value'=>$cl_client,'empty'=>array(''=>$i18n->get('dropdown.select')))); } // Dropdown for projects. -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - $projects = ttTeamHelper::getActiveProjects($user->team_id); - $form->addInput(array('type'=>'combobox','name'=>'project','style'=>'width: 250px;','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_project,'empty'=>array(''=>$i18n->getKey('dropdown.all')))); +$show_project = MODE_PROJECTS == $user->getTrackingMode() || MODE_PROJECTS_AND_TASKS == $user->getTrackingMode(); +if ($show_project) { + $projects = ttGroupHelper::getActiveProjects(); + $form->addInput(array('type'=>'combobox','name'=>'project','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_project,'empty'=>array(''=>$i18n->get('dropdown.all')))); } -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'number','style'=>'width: 250px;','value'=>$cl_number)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'number','value'=>$cl_number)); $form->addInput(array('type'=>'datefield','maxlength'=>'20','name'=>'start','value'=>$cl_start)); $form->addInput(array('type'=>'datefield','maxlength'=>'20','name'=>'finish','value'=>$cl_finish)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.add'))); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.add'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_number)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.invoice.number')); - if (!ttValidDate($cl_date)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.date')); - if (!$cl_client) $err->add($i18n->getKey('error.client')); - if (!ttValidDate($cl_start)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.start_date')); - if (!ttValidDate($cl_finish)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.end_date')); + if (!ttValidString($cl_number)) $err->add($i18n->get('error.field'), $i18n->get('form.invoice.number')); + if (!ttValidDate($cl_date)) $err->add($i18n->get('error.field'), $i18n->get('label.date')); + if (!$cl_client) $err->add($i18n->get('error.client')); + if (!ttValidDate($cl_start)) $err->add($i18n->get('error.field'), $i18n->get('label.start_date')); + if (!ttValidDate($cl_finish)) $err->add($i18n->get('error.field'), $i18n->get('label.end_date')); $fields = array('date'=>$cl_date,'name'=>$cl_number,'client_id'=>$cl_client,'project_id'=>$cl_project,'start_date'=>$cl_start,'end_date'=>$cl_finish); if ($err->no()) { if (ttInvoiceHelper::getInvoiceByName($cl_number)) - $err->add($i18n->getKey('error.invoice_exists')); + $err->add($i18n->get('error.invoice_exists')); if (!ttInvoiceHelper::invoiceableItemsExist($fields)) - $err->add($i18n->getKey('error.no_invoiceable_items')); + $err->add($i18n->get('error.no_invoiceable_items')); } if ($err->no()) { @@ -87,13 +71,14 @@ header('Location: invoices.php'); exit(); } else { - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.invoiceForm.number.focus()"'); -$smarty->assign('title', $i18n->getKey('title.add_invoice')); +$smarty->assign('show_project', $show_project); +$smarty->assign('title', $i18n->get('title.add_invoice')); $smarty->assign('content_page_name', 'invoice_add.tpl'); $smarty->display('index.tpl'); diff --git a/invoice_delete.php b/invoice_delete.php index 41f7c1901..5b473dfa0 100644 --- a/invoice_delete.php +++ b/invoice_delete.php @@ -1,64 +1,46 @@ isPluginEnabled('iv')) { +// Access checks. +if (!ttAccessAllowed('manage_invoices')) { header('Location: access_denied.php'); exit(); } - +if (!$user->isPluginEnabled('iv')) { + header('Location: feature_disabled.php'); + exit(); +} $cl_invoice_id = (int)$request->getParameter('id'); $invoice = ttInvoiceHelper::getInvoice($cl_invoice_id); +if (!$invoice) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + $invoice_to_delete = $invoice['name']; $form = new Form('invoiceDeleteForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_invoice_id)); $form->addInput(array('type'=>'combobox', 'name'=>'delete_invoice_entries', - 'data'=>array('0'=>$i18n->getKey('dropdown.do_not_delete'),'1'=>$i18n->getKey('dropdown.delete')), + 'data'=>array('0'=>$i18n->get('dropdown.do_not_delete'),'1'=>$i18n->get('dropdown.delete')), )); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'),'onclick'=>'return confirm_deleting_entries();')); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); if ($request->isPost()) { if ($request->getParameter('btn_delete')) { - if (ttInvoiceHelper::getInvoice($cl_invoice_id)) { - if (ttInvoiceHelper::delete($cl_invoice_id, $request->getParameter('delete_invoice_entries'))) { - header('Location: invoices.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); + if (ttInvoiceHelper::delete($cl_invoice_id, $request->getParameter('delete_invoice_entries'))) { + header('Location: invoices.php'); + exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } elseif ($request->getParameter('btn_cancel')) { header('Location: invoices.php'); exit(); @@ -68,6 +50,6 @@ $smarty->assign('invoice_to_delete', $invoice_to_delete); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.invoiceDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->getKey('title.delete_invoice')); +$smarty->assign('title', $i18n->get('title.delete_invoice')); $smarty->assign('content_page_name', 'invoice_delete.tpl'); $smarty->display('index.tpl'); diff --git a/invoice_send.php b/invoice_send.php index bc290a666..5483a09b1 100644 --- a/invoice_send.php +++ b/invoice_send.php @@ -1,49 +1,30 @@ isPluginEnabled('iv')) { +// Access checks. +if (!(ttAccessAllowed('manage_invoices') || ttAccessAllowed('view_client_invoices'))) { header('Location: access_denied.php'); exit(); } - +if (!$user->isPluginEnabled('iv')) { + header('Location: feature_disabled.php'); + exit(); +} $cl_invoice_id = (int)$request->getParameter('id'); -$invoice = ttInvoiceHelper::getInvoice($cl_invoice_id); -$sc = new ttSysConfig($user->id); +$invoice = ttInvoiceHelper::getInvoice($cl_invoice_id); +if (!$invoice) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. -// Security check. -if (!$cl_invoice_id || !$invoice) - die ($i18n->getKey('error.sys')); +$uc = new ttUserConfig(); if ($request->isPost()) { $cl_receiver = trim($request->getParameter('receiver')); @@ -51,30 +32,30 @@ $cl_subject = trim($request->getParameter('subject')); $cl_comment = trim($request->getParameter('comment')); } else { - $cl_receiver = $sc->getValue(SYSC_LAST_INVOICE_EMAIL); - $cl_cc = $sc->getValue(SYSC_LAST_INVOICE_CC); - $cl_subject = $i18n->getKey('title.invoice').' '.$invoice['name'].', '.$user->team; + $cl_receiver = $uc->getValue(SYSC_LAST_INVOICE_EMAIL); + $cl_cc = $uc->getValue(SYSC_LAST_INVOICE_CC); + $cl_subject = $i18n->get('title.invoice').' '.$invoice['name'].', '.$user->group_name; } $form = new Form('mailForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_invoice_id)); -$form->addInput(array('type'=>'text','name'=>'receiver','style'=>'width: 300px;','value'=>$cl_receiver)); -$form->addInput(array('type'=>'text','name'=>'cc','style'=>'width: 300px;','value'=>$cl_cc)); -$form->addInput(array('type'=>'text','name'=>'subject','style'=>'width: 300px;','value'=>$cl_subject)); -$form->addInput(array('type'=>'textarea','name'=>'comment','maxlength'=>'250','style'=>'width: 300px; height: 60px;')); -$form->addInput(array('type'=>'submit','name'=>'btn_send','value'=>$i18n->getKey('button.send'))); +$form->addInput(array('type'=>'text','name'=>'receiver','value'=>$cl_receiver)); +$form->addInput(array('type'=>'text','name'=>'cc','value'=>$cl_cc)); +$form->addInput(array('type'=>'text','name'=>'subject','value'=>$cl_subject)); +$form->addInput(array('type'=>'textarea','name'=>'comment','maxlength'=>'250')); +$form->addInput(array('type'=>'submit','name'=>'btn_send','value'=>$i18n->get('button.send'))); if ($request->isPost()) { // Validate user input. - if (!ttValidEmailList($cl_receiver)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.to')); - if (!ttValidEmailList($cl_cc, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.cc')); - if (!ttValidString($cl_subject)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.subject')); - if (!ttValidString($cl_comment, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.comment')); + if (!ttValidEmailList($cl_receiver)) $err->add($i18n->get('error.field'), $i18n->get('form.mail.to')); + if (!ttValidEmailList($cl_cc, true)) $err->add($i18n->get('error.field'), $i18n->get('label.cc')); + if (!ttValidString($cl_subject)) $err->add($i18n->get('error.field'), $i18n->get('label.subject')); + if (!ttValidString($cl_comment, true)) $err->add($i18n->get('error.field'), $i18n->get('label.comment')); if ($err->no()) { // Save last invoice emails for future use. - $sc->setValue(SYSC_LAST_INVOICE_EMAIL, $cl_receiver); - $sc->setValue(SYSC_LAST_INVOICE_CC, $cl_cc); + $uc->setValue(SYSC_LAST_INVOICE_EMAIL, $cl_receiver); + $uc->setValue(SYSC_LAST_INVOICE_CC, $cl_cc); $body = ttInvoiceHelper::prepareInvoiceBody($cl_invoice_id, $cl_comment); @@ -86,25 +67,17 @@ $mailer->setReceiver($cl_receiver); if (isset($cl_cc)) $mailer->setReceiverCC($cl_cc); + if (!empty($user->bcc_email)) + $mailer->setReceiverBCC($user->bcc_email); $mailer->setMailMode(MAIL_MODE); if ($mailer->send($cl_subject, $body)) - $msg->add($i18n->getKey('form.mail.invoice_sent')); + $msg->add($i18n->get('form.mail.invoice_sent')); else - $err->add($i18n->getKey('error.mail_send')); + $err->add($i18n->get('error.mail_send')); } } // isPost -$smarty->assign('sender', SENDER); -if (function_exists('imap_mime_header_decode')) { - // Decode sender in case it is encoded. PHP IMAP extension must be installed for us to get here. - $elements = imap_mime_header_decode(SENDER); - if (count($elements) > 1) { - // Reassign sender. - $smarty->assign('sender', $elements[count($elements) - 2]->text); - } -} - -$smarty->assign('title', $i18n->getKey('title.send_invoice')); +$smarty->assign('title', $i18n->get('title.send_invoice')); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.mailForm.'.($cl_receiver?'comment':'receiver').'.focus()"'); $smarty->assign('content_page_name', 'mail.tpl'); diff --git a/invoice_view.php b/invoice_view.php index e94c04b72..b1fcf91f9 100644 --- a/invoice_view.php +++ b/invoice_view.php @@ -1,57 +1,43 @@ isPluginEnabled('iv')) { +// Access checks. +if (!(ttAccessAllowed('manage_invoices') || ttAccessAllowed('view_client_invoices'))) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('iv')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_invoice_id = (int)$request->getParameter('id'); +$invoice = ttInvoiceHelper::getInvoice($cl_invoice_id); +if (!$invoice) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. -$invoice_id = (int)$request->getParameter('id'); -$invoice = ttInvoiceHelper::getInvoice($invoice_id); -$invoice_date = new DateAndTime(DB_DATEFORMAT, $invoice['date']); +$invoice_date = new ttDate($invoice['date']); $client = ttClientHelper::getClient($invoice['client_id'], true); if (!$client) // In case client was deleted. $client = ttClientHelper::getDeletedClient($invoice['client_id']); -$invoice_items = ttInvoiceHelper::getInvoiceItems($invoice_id); +$invoice_items = ttInvoiceHelper::getInvoiceItems($cl_invoice_id); $tax_percent = $client['tax']; $subtotal = 0; $tax = 0; foreach($invoice_items as $item) $subtotal += $item['cost']; -if ($tax_percent) { +if ($tax_percent > 0) { $tax_expenses = $user->isPluginEnabled('et'); foreach($invoice_items as $item) { if ($item['type'] == 2 && !$tax_expenses) @@ -59,31 +45,67 @@ $tax += round($item['cost'] * $tax_percent / 100, 2); } } -$total = $subtotal + $tax; +$total = $subtotal + $tax; + +$currency = $user->getCurrency(); +$decimalMark = $user->getDecimalMark(); -$smarty->assign('subtotal', $user->currency.' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($subtotal, 2)))); -if ($tax) $smarty->assign('tax', $user->currency.' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($tax, 2)))); -$smarty->assign('total', $user->currency.' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($total, 2)))); +$smarty->assign('subtotal', $currency.' '.str_replace('.', $decimalMark, sprintf('%8.2f', round($subtotal, 2)))); +if ($tax) $smarty->assign('tax', $currency.' '.str_replace('.', $decimalMark, sprintf('%8.2f', round($tax, 2)))); +$smarty->assign('total', $currency.' '.str_replace('.', $decimalMark, sprintf('%8.2f', round($total, 2)))); -if ('.' != $user->decimal_mark) { +if ('.' != $decimalMark) { foreach ($invoice_items as &$item) - $item['cost'] = str_replace('.', $user->decimal_mark, $item['cost']); + $item['cost'] = str_replace('.', $decimalMark, $item['cost']); } // Calculate colspan for invoice summary. $colspan = 4; -if (MODE_PROJECTS == $user->tracking_mode) +$trackingMode = $user->getTrackingMode(); +if (MODE_PROJECTS == $trackingMode) $colspan++; -elseif (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) +elseif (MODE_PROJECTS_AND_TASKS == $trackingMode) $colspan += 2; -$smarty->assign('invoice_id', $invoice_id); +$form = new Form('invoiceForm'); +// Hidden control for invoice id. +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_invoice_id)); +// invoiceForm only contains controls for "Mark paid" block below invoice table. +if ($user->isPluginEnabled('ps') && !$user->isClient()) { + $mark_paid_action_options = array('1'=>$i18n->get('dropdown.paid'),'2'=>$i18n->get('dropdown.not_paid')); + $form->addInput(array('type'=>'combobox', + 'name'=>'mark_paid_action_options', + 'class'=>'dropdown-field-with-button', + 'data'=>$mark_paid_action_options)); + $form->addInput(array('type'=>'submit','name'=>'btn_mark_paid','value'=>$i18n->get('button.submit'))); + $smarty->assign('show_mark_paid', true); +} + +if ($request->isPost()) { + if ($request->getParameter('btn_mark_paid')) { + // User clicked the "Mark paid" button to mark all invoice items either paid or not paid. + + // Determine user action. + $mark_paid = $request->getParameter('mark_paid_action_options') == 1 ? true : false; + ttInvoiceHelper::markPaid($cl_invoice_id, $mark_paid); + + // Re-display this form. + header('Location: invoice_view.php?id='.$cl_invoice_id); + exit(); + } +} + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('invoice_id', $cl_invoice_id); $smarty->assign('invoice_name', $invoice['name']); -$smarty->assign('invoice_date', $invoice_date->toString($user->date_format)); +$smarty->assign('invoice_date', $invoice_date->toString($user->getDateFormat())); $smarty->assign('client_name', $client['name']); $smarty->assign('client_address', $client['address']); +$smarty->assign('show_project', MODE_PROJECTS == $trackingMode || MODE_PROJECTS_AND_TASKS == $trackingMode); +$smarty->assign('show_task', MODE_PROJECTS_AND_TASKS == $trackingMode); +$smarty->assign('show_paid_column', $user->isPluginEnabled('ps')); $smarty->assign('invoice_items', $invoice_items); $smarty->assign('colspan', $colspan); -$smarty->assign('title', $i18n->getKey('title.view_invoice')); +$smarty->assign('title', $i18n->get('title.view_invoice')); $smarty->assign('content_page_name', 'invoice_view.tpl'); $smarty->display('index.tpl'); diff --git a/invoices.php b/invoices.php index a014472f3..d34e1220e 100644 --- a/invoices.php +++ b/invoices.php @@ -1,44 +1,94 @@ isPluginEnabled('iv')) { +// Access checks. +if (!(ttAccessAllowed('manage_invoices') || ttAccessAllowed('view_client_invoices'))) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('iv')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. + +$sort_option_1 = $sort_order_1 = $sort_option_2 = $sort_order_2 = null; +if ($request->isPost()) { + $sort_option_1 = $request->getParameter('sort_option_1'); + $sort_order_1 = $request->getParameter('sort_order_1'); + $sort_option_2 = $request->getParameter('sort_option_2'); + $sort_order_2 = $request->getParameter('sort_order_2'); +} + +$invoices = ttGroupHelper::getActiveInvoices(); + +$form = new Form('invoicesForm'); + +// Prepare an array of sort options. +$sort_options['name'] = $i18n->get('label.invoice'); +$sort_options['client'] = $i18n->get('label.client'); +$sort_options['date'] = $i18n->get('label.date'); -$invoices = ttTeamHelper::getActiveInvoices(); +$form->addInput(array('type'=>'combobox', + 'name'=>'sort_option_1', + 'class'=>'dropdown-field-with-button', + 'onchange'=>'this.form.sorting_changed.value=1;this.form.submit();', + 'data'=>$sort_options, + 'value'=>$sort_option_1)); +$form->addInput(array('type'=>'combobox', + 'name'=>'sort_option_2', + 'class'=>'dropdown-field-with-button', + 'onchange'=>'this.form.sorting_changed.value=1;this.form.submit();', + 'data'=>$sort_options, + 'value'=>$sort_option_2, + 'empty'=>array(''=>$i18n->get('dropdown.no')))); + +// Prepare an array of sort order. +$sort_order['ascending'] = $i18n->get('dropdown.ascending'); +$sort_order['descending'] = $i18n->get('dropdown.descending'); + +$form->addInput(array('type'=>'combobox', + 'name'=>'sort_order_1', + 'class'=>'dropdown-field-with-button', + 'onchange'=>'this.form.sorting_changed.value=1;this.form.submit();', + 'data'=>$sort_order, + 'value'=>$sort_order_1)); +$form->addInput(array('type'=>'combobox', + 'name'=>'sort_order_2', + 'class'=>'dropdown-field-with-button', + 'onchange'=>'this.form.sorting_changed.value=1;this.form.submit();', + 'data'=>$sort_order, + 'value'=>$sort_order_2)); + + +$form->addInput(array('type'=>'hidden','name'=>'sorting_changed')); + +if ($request->isPost()) { + // Validate user input. + if (!ttInvoiceHelper::validSortOption($sort_option_1)) $err->add($i18n->get('error.field'), $i18n->get('label.sort')); + if (!ttInvoiceHelper::validSortOption($sort_option_2, true)) $err->add($i18n->get('error.field'), $i18n->get('label.sort')); + if (!ttInvoiceHelper::validSortOrder($sort_order_1)) $err->add($i18n->get('error.field'), $i18n->get('label.sort')); + if (!ttInvoiceHelper::validSortOrder($sort_order_2)) $err->add($i18n->get('error.field'), $i18n->get('label.sort')); + if ($sort_option_1 == $sort_option_2) $err->add($i18n->get('error.field'), $i18n->get('label.sort')); + + if ($err->no() && $request->getParameter('sorting_changed')) { + // User changed sorting. Get invoices sorted accordingly. + $sort_options = array('sort_option_1'=>$sort_option_1, + 'sort_order_1'=>$sort_order_1, + 'sort_option_2'=>$sort_option_2, + 'sort_order_2'=>$sort_order_2); + $invoices = ttGroupHelper::getActiveInvoices($sort_options); + } +} $smarty->assign('invoices', $invoices); -$smarty->assign('title', $i18n->getKey('title.invoices')); +$smarty->assign('show_sorting_options', count($invoices) > 1); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.invoices')); $smarty->assign('content_page_name', 'invoices.tpl'); $smarty->display('index.tpl'); diff --git a/license.txt b/license.txt index eaa424c64..5c63aca6f 100644 --- a/license.txt +++ b/license.txt @@ -1,20 +1,171 @@ -Anuko Time Tracker +Server Side Public License -Copyright Anuko International Ltd. (https://www.anuko.com) +VERSION 1, OCTOBER 16, 2018 -LIBERAL FREEWARE LICENSE: This source code document may be used -by anyone for any purpose, and freely redistributed alone or in -combination with other software, provided that the license is obeyed. +Copyright © 2018 MongoDB, Inc. -There are only two ways to violate the license: +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. +TERMS AND CONDITIONS +0. Definitions. -1. To redistribute this code in source form, with the copyright notice or - license removed or altered. (Distributing in compiled forms without - embedded copyright notices is permitted). +“This License†refers to Server Side Public License. -2. To redistribute modified versions of this code in *any* form - that bears insufficient indications that the modifications are - not the work of the original author(s). +“Copyright†also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +“The Program†refers to any copyrightable work licensed under this License. Each licensee is addressed as “youâ€. “Licensees†and “recipients†may be individuals or organizations. + +To “modify†a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version†of the earlier work or a work “based on†the earlier work. + +A “covered work†means either the unmodified Program or a work based on the Program. + +To “propagate†a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To “convey†a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices†to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. +1. Source Code. + +The “source code†for a work means the preferred form of the work for making modifications to it. “Object code†means any non-source form of a work. + +A “Standard Interface†means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The “System Libraries†of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Componentâ€, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The “Corresponding Source†for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. +2. Basic Permissions. + +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program, subject to section 13. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +Subject to section 13, you may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all noticesâ€. + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate†if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A “User Product†is either (1) a “consumer productâ€, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used†refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +“Installation Information†for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. +7. Additional Terms. + +“Additional permissions†are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further restrictions†within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. +8. Termination. + +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An “entity transaction†is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. +11. Patents. + +A “contributor†is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor versionâ€. + +A contributor's “essential patent claims†are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control†includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license†is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant†such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying†means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is “discriminatory†if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot use, propagate or convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not use, propagate or convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. +13. Offering the Program as a Service. + +If you make the functionality of the Program or a modified version available to third parties as a service, you must make the Service Source Code available via network download to everyone at no charge, under the terms of this License. Making the functionality of the Program or modified version available to third parties as a service includes, without limitation, enabling third parties to interact with the functionality of the Program or modified version remotely through a computer network, offering a service the value of which entirely or primarily derives from the value of the Program or modified version, or offering a service that accomplishes for users the primary purpose of the Program or modified version. + +“Service Source Code†means the Corresponding Source for the Program or the modified version, and the Corresponding Source for all programs that you use to make the Program or modified version available as a service, including, without limitation, management software, user interfaces, application program interfaces, automation software, monitoring software, backup software, storage software and hosting software, all such that a user could run an instance of the service using the Service Source Code you make available. +14. Revised Versions of this License. + +MongoDB, Inc. may publish revised and/or new versions of the Server Side Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the Server Side Public License “or any later version†applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by MongoDB, Inc. If the Program does not specify a version number of the Server Side Public License, you may choose any version ever published by MongoDB, Inc. + +If the Program specifies that a proxy can decide which future versions of the Server Side Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS†WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS -This license applies to this document only, not any other software that it -may be combined with. diff --git a/locking.php b/locking.php index 8acdcd704..04c8d9419 100644 --- a/locking.php +++ b/locking.php @@ -1,64 +1,41 @@ isPluginEnabled('lk')) { +// Access checks. +if (!ttAccessAllowed('manage_advanced_settings')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('lk')) { + header('Location: feature_disabled.php'); + exit(); +} -$cl_lock_spec = $request->isPost() ? $request->getParameter('lock_spec') : $user->lock_spec; +$cl_lock_spec = $request->isPost() ? $request->getParameter('lock_spec') : $user->getLockSpec(); $form = new Form('lockingForm'); $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'lock_spec','style'=>'width: 250px;','value'=>$cl_lock_spec)); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); if ($request->isPost()) { // Validate user input. - if (!ttValidCronSpec($cl_lock_spec)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.cron_schedule')); + if (!ttValidCronSpec($cl_lock_spec)) $err->add($i18n->get('error.field'), $i18n->get('label.schedule')); if ($err->no()) { - if (ttTeamHelper::update($user->team_id, array( - 'name' => $user->team, - 'lock_spec' => $cl_lock_spec))) { - header('Location: profile_edit.php'); + if ($user->updateGroup(array('lock_spec' => $cl_lock_spec))) { + header('Location: group_edit.php'); exit(); } else { - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.locking')); +$smarty->assign('title', $i18n->get('title.locking')); $smarty->assign('content_page_name', 'locking.tpl'); $smarty->display('index.tpl'); diff --git a/login.php b/login.php index d578ae94b..2d9dde044 100644 --- a/login.php +++ b/login.php @@ -1,91 +1,149 @@ isPost()) { + // Validate that browser_today parameter is in correct format. + $browser_today = $request->getParameter('browser_today'); + if ($browser_today && !ttValidDbDateFormatDate($browser_today)) { + header('Location: access_denied.php'); + exit(); + } +} +// End of access checks. $cl_login = $request->getParameter('login'); +if ($cl_login == null && $request->isGet()) $cl_login = @$_COOKIE[LOGIN_COOKIE_NAME]; $cl_password = $request->getParameter('password'); -if ($cl_login == null && $request->getMethod() == 'GET') - $cl_login = @$_COOKIE['tt_login']; $form = new Form('loginForm'); -$form->addInput(array('type'=>'text','size'=>'25','maxlength'=>'100','name'=>'login','style'=>'width: 220px;','value'=>$cl_login)); -$form->addInput(array('type'=>'text','size'=>'25','maxlength'=>'50','name'=>'password','style'=>'width: 220px;','aspassword'=>true,'value'=>$cl_password)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login)); +$form->addInput(array('type'=>'password','maxlength'=>'50','name'=>'password','value'=>$cl_password)); $form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_login click. -$form->addInput(array('type'=>'submit','name'=>'btn_login','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.login'))); +$form->addInput(array('type'=>'submit','name'=>'btn_login','onclick'=>'browser_today.value=get_date()','value'=>$i18n->get('button.login'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); - if (!ttValidString($cl_password)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); + if (!ttValidString($cl_login)) $err->add($i18n->get('error.field'), $i18n->get('label.login')); + if (!ttValidString($cl_password)) $err->add($i18n->get('error.field'), $i18n->get('label.password')); + + $loginSucceeded = $use2FA = false; if ($err->no()) { // Use the "limit" plugin if we have one. Ignore include errors. // The "limit" plugin is not required for normal operation of Time Tracker. @include('plugins/limit/access_check.php'); - if ($auth->doLogin($cl_login, $cl_password)) { - // Set current user date (as determined by user browser) into session. - $current_user_date = $request->getParameter('browser_today', null); - if ($current_user_date) - $_SESSION['date'] = $current_user_date; - + // Check user login. + $loginSucceeded = $auth->doLogin($cl_login, $cl_password); + if ($loginSucceeded) { // Remember user login in a cookie. - setcookie('tt_login', $cl_login, time() + COOKIE_EXPIRE, '/'); - - $user = new ttUser(null, $auth->getUserId()); - // Redirect, depending on user role. - if ($user->isAdmin()) { - header('Location: admin_teams.php'); - } elseif ($user->isClient()) { - header('Location: reports.php'); - } else { - header('Location: time.php'); - } - exit(); + setcookie(LOGIN_COOKIE_NAME, $cl_login, time() + COOKIE_EXPIRE, '/'); + } else { + $err->add($i18n->get('error.auth')); + } + } + + // Do we have to use 2FA? + if ($err->no() && $loginSucceeded) { + $user = new ttUser(null, $auth->getUserId()); + + if ($user->initialized) { + // Determine if we have to additionally use two-factor authentication. + $config = $user->getConfigHelper(); + $use2FA = $config->getDefinedValue('2fa'); + } else { + $err->add($i18n->get('error.db')); + } + } + + // If we have to use 2FA, email auth code to user and redirect to 2fa.php. + if ($err->no() && $use2FA && !$user->can('override_2fa')) { + // To keep things simple, we use the same code as for password resets. + $cryptographically_strong = true; + $random_bytes = openssl_random_pseudo_bytes(16, $cryptographically_strong); + if ($random_bytes === false) die ("openssl_random_pseudo_bytes function call failed..."); + $temp_ref = bin2hex($random_bytes); + ttUserHelper::saveTmpRef($temp_ref, $user->id); + + // For user languague in email. + $user_i18n = null; + if ($user->lang != $i18n->lang) { + $user_i18n = new I18n(); + $user_i18n->load($user->lang); } else - $err->add($i18n->getKey('error.auth')); + $user_i18n = &$i18n; + + // Where do we email to? + $receiver = null; + if ($user->email) + $receiver = $user->email; + else { + if (ttValidEmail($user->login)) + $receiver = $user->login; + } + if (!$receiver) $err->add($user_i18n->get('error.no_email')); + + // Send 2FA_code email to user. + if ($receiver) { + import('mail.Mailer'); + $mailer = new Mailer(); + $mailer->setCharSet(CHARSET); + $mailer->setSender(SENDER); + $mailer->setReceiver("$receiver"); + + $subject = $user_i18n->get('email.2fa_code.subject'); + $body = sprintf($user_i18n->get('email.2fa_code.body'), $temp_ref); + + $mailer->setMailMode(MAIL_MODE); + if (!$mailer->send($subject, $body)) + $err->add($i18n->get('error.mail_send')); + } + + $auth->doLogout(); + + // Redirect to 2fa.php if we have no errors. + if ($err->no()) { + header('Location: 2fa.php'); + exit(); + } + } + + if ($err->no() && $loginSucceeded) { + // Set current user date (as determined by user browser) into session. + $current_user_date = $request->getParameter('browser_today', null); + if ($current_user_date) + $_SESSION['date'] = $current_user_date; + + // Redirect, depending on user role. + if ($user->can('administer_site')) { + header('Location: admin_groups.php'); + } elseif ($user->isClient()) { + header('Location: reports.php'); + } else { + header('Location: time.php'); + } + exit(); } } // isPost -if(!isTrue(MULTITEAM_MODE) && !ttTeamHelper::getTeams()) - $err->add($i18n->getKey('error.no_teams')); +if(!isTrue('MULTIORG_MODE') && !ttOrgHelper::getOrgs()) + $err->add($i18n->get('error.no_groups')); // Determine whether to show login hint. It is currently used only for Windows LDAP authentication. -$show_hint = ('ad' == $GLOBALS['AUTH_MODULE_PARAMS']['type']); +$show_hint = ('ad' == isset($GLOBALS['AUTH_MODULE_PARAMS']['type']) ? $GLOBALS['AUTH_MODULE_PARAMS']['type'] : null); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('show_hint', $show_hint); $smarty->assign('onload', 'onLoad="document.loginForm.'.(!$cl_login?'login':'password').'.focus()"'); -$smarty->assign('title', $i18n->getKey('title.login')); +$smarty->assign('about_text', $i18n->get('form.login.about')); +$smarty->assign('title', $i18n->get('title.login')); $smarty->assign('content_page_name', 'login.tpl'); -$smarty->assign('about_text', $i18n->getKey('form.login.about')); $smarty->display('index.tpl'); diff --git a/logout.php b/logout.php index e2c6cfedc..116bbe428 100644 --- a/logout.php +++ b/logout.php @@ -1,30 +1,6 @@ doLogout(); diff --git a/mobile/access_denied.php b/mobile/access_denied.php deleted file mode 100644 index 836579a5a..000000000 --- a/mobile/access_denied.php +++ /dev/null @@ -1,36 +0,0 @@ -add($i18n->getKey('error.access_denied')); -if ($auth->isAuthenticated()) $GLOBALS['SMARTY']->assign('authenticated', true); // Used in header.tpl for menu display. - -$smarty->assign('title', $i18n->getKey('label.error')); -$smarty->assign('content_page_name', 'mobile/access_denied.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/client_add.php b/mobile/client_add.php deleted file mode 100644 index e48949627..000000000 --- a/mobile/client_add.php +++ /dev/null @@ -1,89 +0,0 @@ -isPluginEnabled('cl')) { - header('Location: access_denied.php'); - exit(); -} - -$projects = ttTeamHelper::getActiveProjects($user->team_id); - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('name')); - $cl_address = trim($request->getParameter('address')); - $cl_tax = $request->getParameter('tax'); - $cl_projects = $request->getParameter('projects'); -} else { - // Do not assign all projects to a new client by default. This should help to reduce clutter. - // foreach ($projects as $project_item) - // $cl_projects[] = $project_item['id']; -} - -$form = new Form('clientForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','class'=>'mobile-textarea','value'=>$cl_address)); -$form->addInput(array('type'=>'floatfield','name'=>'tax','size'=>'10','format'=>'.2','value'=>$cl_tax)); -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) - $form->addInput(array('type'=>'checkboxgroup','name'=>'projects','data'=>$projects,'layout'=>'H','datakeys'=>array('id','name'),'value'=>$cl_projects)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.add'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_name')); - if (!ttValidString($cl_address, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_address')); - if (!ttValidFloat($cl_tax, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.tax')); - - if ($err->no()) { - if (!ttClientHelper::getClientByName($cl_name)) { - if (ttClientHelper::insert(array( - 'team_id' => $user->team_id, - 'name' => $cl_name, - 'address' => $cl_address, - 'tax' => $cl_tax, - 'projects' => $cl_projects, - 'status' => ACTIVE))) { - header('Location: clients.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.client_exists')); - } -} // isPost - -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.clientForm.name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.add_client')); -$smarty->assign('content_page_name', 'mobile/client_add.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/client_delete.php b/mobile/client_delete.php deleted file mode 100644 index 51e87b43e..000000000 --- a/mobile/client_delete.php +++ /dev/null @@ -1,73 +0,0 @@ -isPluginEnabled('cl')) { - header('Location: access_denied.php'); - exit(); -} - -$id = (int)$request->getParameter('id'); -$client = ttClientHelper::getClient($id); - -$client_to_delete = $client['name']; - -$form = new Form('clientDeleteForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$id)); -$form->addInput(array('type'=>'combobox','name'=>'delete_client_entries', - 'data'=>array('0'=>$i18n->getKey('dropdown.do_not_delete'),'1'=>$i18n->getKey('dropdown.delete')))); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); - -if ($request->isPost()) { - if(ttClientHelper::getClient($id)) { - if ($request->getParameter('btn_delete')) { - if (ttClientHelper::delete($id, $request->getParameter('delete_client_entries'))) { - header('Location: clients.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } - } else - $err->add($i18n->getKey('error.db')); - - if ($request->getParameter('btn_cancel')) { - header('Location: clients.php'); - exit(); - } -} // isPost - -$smarty->assign('client_to_delete', $client_to_delete); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.delete_client')); -$smarty->assign('content_page_name', 'mobile/client_delete.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/client_edit.php b/mobile/client_edit.php deleted file mode 100644 index ac3e60c71..000000000 --- a/mobile/client_edit.php +++ /dev/null @@ -1,127 +0,0 @@ -isPluginEnabled('cl')) { - header('Location: access_denied.php'); - exit(); -} - -$cl_id = (int) $request->getParameter('id'); - -$projects = ttTeamHelper::getActiveProjects($user->team_id); - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('name')); - $cl_address = trim($request->getParameter('address')); - $cl_tax = trim($request->getParameter('tax')); - $cl_status = $request->getParameter('status'); - $cl_projects = $request->getParameter('projects'); -} else { - $client = ttClientHelper::getClient($cl_id, true); - $cl_name = $client['name']; - $cl_address = $client['address']; - $cl_tax = $client['tax']; - $cl_status = $client['status']; - $assigned_projects = ttClientHelper::getAssignedProjects($cl_id); - foreach($assigned_projects as $project_item) { - $cl_projects[] = $project_item['id']; - } -} - -$form = new Form('clientForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); -$form->addInput(array('type'=>'text','name'=>'name','maxlength'=>'100','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','class'=>'mobile-textarea','value'=>$cl_address)); -$form->addInput(array('type'=>'floatfield','name'=>'tax','size'=>'10','format'=>'.2','value'=>$cl_tax)); -$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status, - 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive')))); -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) - $form->addInput(array('type'=>'checkboxgroup','name'=>'projects','data'=>$projects,'datakeys'=>array('id','name'),'layout'=>'H','value'=>$cl_projects)); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->getKey('button.copy'))); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_name')); - if (!ttValidString($cl_address, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.client_address')); - if (!ttValidFloat($cl_tax, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.tax')); - - if ($err->no()) { - if ($request->getParameter('btn_save')) { - $client = ttClientHelper::getClientByName($cl_name); - if (($client && ($cl_id == $client['id'])) || !$client) { - if (ttClientHelper::update(array( - 'id' => $cl_id, - 'name' => $cl_name, - 'address' => $cl_address, - 'tax' => $cl_tax, - 'status' => $cl_status, - 'projects' => $cl_projects))) { - header('Location: clients.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.client_exists')); - } - - if ($request->getParameter('btn_copy')) { - if (!ttClientHelper::getClientByName($cl_name)) { - if (ttClientHelper::insert(array( - 'team_id' => $user->team_id, - 'name' => $cl_name, - 'address' => $cl_address, - 'tax' => $cl_tax, - 'status' => $cl_status, - 'projects' => $cl_projects))) { - header('Location: clients.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.client_exists')); - } - - if ($request->getParameter('btn_delete')) { - header("Location: client_delete.php?id=$cl_id"); - exit(); - } - } -} // isPost - -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.edit_client')); -$smarty->assign('content_page_name', 'mobile/client_edit.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/clients.php b/mobile/clients.php deleted file mode 100644 index 6312c2dd4..000000000 --- a/mobile/clients.php +++ /dev/null @@ -1,43 +0,0 @@ -isPluginEnabled('cl')) { - header('Location: access_denied.php'); - exit(); -} - -$smarty->assign('active_clients', ttTeamHelper::getActiveClients($user->team_id, true)); -$smarty->assign('inactive_clients', ttTeamHelper::getInactiveClients($user->team_id, true)); -$smarty->assign('title', $i18n->getKey('title.clients')); -$smarty->assign('content_page_name', 'mobile/clients.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/expense_delete.php b/mobile/expense_delete.php deleted file mode 100644 index 8e89513fd..000000000 --- a/mobile/expense_delete.php +++ /dev/null @@ -1,78 +0,0 @@ -isPluginEnabled('ex')) { - header('Location: access_denied.php'); - exit(); -} - -$cl_id = $request->getParameter('id'); -$expense_item = ttExpenseHelper::getItem($cl_id, $user->getActiveUser()); - -// Prohibit deleting invoiced records. -if ($expense_item['invoice_id']) die($i18n->getKey('error.sys')); - -if ($request->isPost()) { - if ($request->getParameter('delete_button')) { // Delete button pressed. - - // Determine if it is okay to delete the record. - $item_date = new DateAndTime(DB_DATEFORMAT, $expense_item['date']); - if ($user->isDateLocked($item_date)) - $err->add($i18n->getKey('error.range_locked')); - - if ($err->no()) { - // Mark the record as deleted. - if (ttExpenseHelper::markDeleted($cl_id, $user->getActiveUser())) { - header('Location: expenses.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } - } - if ($request->getParameter('cancel_button')) { // Cancel button pressed. - header('Location: expenses.php'); - exit(); - } -} // isPost - -$form = new Form('expenseItemForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); -$form->addInput(array('type'=>'submit','name'=>'delete_button','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'cancel_button','value'=>$i18n->getKey('button.cancel'))); - -$smarty->assign('expense_item', $expense_item); -$smarty->assign('forms', array($form->getName() => $form->toArray())); -$smarty->assign('title', $i18n->getKey('title.delete_expense')); -$smarty->assign('content_page_name', 'mobile/expense_delete.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/expense_edit.php b/mobile/expense_edit.php deleted file mode 100644 index c2df1abb6..000000000 --- a/mobile/expense_edit.php +++ /dev/null @@ -1,207 +0,0 @@ -isPluginEnabled('ex')) { - header('Location: access_denied.php'); - exit(); -} - -$cl_id = $request->getParameter('id'); - -// Get the expense item we are editing. -$expense_item = ttExpenseHelper::getItem($cl_id, $user->getActiveUser()); - -// Prohibit editing invoiced items. -if ($expense_item['invoice_id']) die($i18n->getKey('error.sys')); - -$item_date = new DateAndTime(DB_DATEFORMAT, $expense_item['date']); - -// Initialize variables. -$cl_date = $cl_client = $cl_project = $cl_item_name = $cl_cost = null; -if ($request->isPost()) { - $cl_date = trim($request->getParameter('date')); - $cl_client = $request->getParameter('client'); - $cl_project = $request->getParameter('project'); - $cl_item_name = trim($request->getParameter('item_name')); - $cl_cost = trim($request->getParameter('cost')); -} else { - $cl_date = $item_date->toString($user->date_format); - $cl_client = $expense_item['client_id']; - $cl_project = $expense_item['project_id']; - $cl_item_name = $expense_item['name']; - $cl_cost = $expense_item['cost']; -} - -// Initialize elements of 'expenseItemForm'. -$form = new Form('expenseItemForm'); - -// Dropdown for clients in MODE_TIME. Use all active clients. -if (MODE_TIME == $user->tracking_mode && $user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillProjectDropdown(this.value);', - 'name'=>'client', - 'style'=>'width: 250px;', - 'value'=>$cl_client, - 'data'=>$active_clients, - 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - // Note: in other modes the client list is filtered to relevant clients only. See below. -} - -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - // Dropdown for projects assigned to user. - $project_list = $user->getAssignedProjects(); - $form->addInput(array('type'=>'combobox', - 'name'=>'project', - 'style'=>'width: 250px;', - 'value'=>$cl_project, - 'data'=>$project_list, - 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - - // Dropdown for clients if the clients plugin is enabled. - if ($user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - // We need an array of assigned project ids to do some trimming. - foreach($project_list as $project) - $projects_assigned_to_user[] = $project['id']; - - // Build a client list out of active clients. Use only clients that are relevant to user. - // Also trim their associated project list to only assigned projects (to user). - foreach($active_clients as $client) { - $projects_assigned_to_client = explode(',', $client['projects']); - $intersection = array_intersect($projects_assigned_to_client, $projects_assigned_to_user); - if ($intersection) { - $client['projects'] = implode(',', $intersection); - $client_list[] = $client; - } - } - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillProjectDropdown(this.value);', - 'name'=>'client', - 'style'=>'width: 250px;', - 'value'=>$cl_client, - 'data'=>$client_list, - 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - } -} -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'item_name','style'=>'width: 250px;','value'=>$cl_item_name)); -$form->addInput(array('type'=>'text','maxlength'=>'40','name'=>'cost','style'=>'width: 100px;','value'=>$cl_cost)); -$form->addInput(array('type'=>'datefield','name'=>'date','maxlength'=>'20','value'=>$cl_date)); -// Hidden control for record id. -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); -$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_save or btn_copy click. -$form->addInput(array('type'=>'submit','name'=>'btn_save','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_copy','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.copy'))); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); - -if ($request->isPost()) { - // Validate user input. - if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client) - $err->add($i18n->getKey('error.client')); - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $err->add($i18n->getKey('error.project')); - } - if (!ttValidString($cl_item_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.item')); - if (!ttValidFloat($cl_cost)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.cost')); - if (!ttValidDate($cl_date)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.date')); - - // This is a new date for the expense item. - $new_date = new DateAndTime($user->date_format, $cl_date); - - // Prohibit creating entries in future. - if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($new_date->after($browser_today)) - $err->add($i18n->getKey('error.future_date')); - } - - // Save record. - if ($request->getParameter('btn_save')) { - // We need to: - // 1) Prohibit updating locked entries (that are in locked range). - // 2) Prohibit saving unlocked entries into locked range. - - // Now, step by step. - // 1) Prohibit saving locked entries in any form. - if ($user->isDateLocked($item_date)) - $err->add($i18n->getKey('error.range_locked')); - - // 2) Prohibit saving unlocked entries into locked range. - if ($err->no() && $user->isDateLocked($new_date)) - $err->add($i18n->getKey('error.range_locked')); - - // Now, an update. - if ($err->no()) { - if (ttExpenseHelper::update(array('id'=>$cl_id,'date'=>$new_date->toString(DB_DATEFORMAT),'user_id'=>$user->getActiveUser(), - 'client_id'=>$cl_client,'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost))) { - header('Location: expenses.php?date='.$new_date->toString(DB_DATEFORMAT)); - exit(); - } - } - } - - // Save as new record. - if ($request->getParameter('btn_copy')) { - // We need to prohibit saving into locked interval. - if ($user->isDateLocked($new_date)) - $err->add($i18n->getKey('error.range_locked')); - - // Now, a new insert. - if ($err->no()) { - if (ttExpenseHelper::insert(array('date'=>$new_date->toString(DB_DATEFORMAT),'user_id'=>$user->getActiveUser(), - 'client_id'=>$cl_client,'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost,'status'=>1))) { - header('Location: expenses.php?date='.$new_date->toString(DB_DATEFORMAT)); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } - } - - if ($request->getParameter('btn_delete')) { - header("Location: expense_delete.php?id=$cl_id"); - exit(); - } -} // isPost - -$smarty->assign('client_list', $client_list); -$smarty->assign('project_list', $project_list); -$smarty->assign('task_list', $task_list); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.edit_expense')); -$smarty->assign('content_page_name', 'mobile/expense_edit.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/expenses.php b/mobile/expenses.php deleted file mode 100644 index fafe1373e..000000000 --- a/mobile/expenses.php +++ /dev/null @@ -1,198 +0,0 @@ -isPluginEnabled('ex')) { - header('Location: access_denied.php'); - exit(); -} - -// Initialize and store date in session. -$cl_date = $request->getParameter('date', @$_SESSION['date']); -$selected_date = new DateAndTime(DB_DATEFORMAT, $cl_date); -if($selected_date->isError()) - $selected_date = new DateAndTime(DB_DATEFORMAT); -if(!$cl_date) - $cl_date = $selected_date->toString(DB_DATEFORMAT); -$_SESSION['date'] = $cl_date; - -// Determine previous and next dates for simple navigation. -$prev_date = date('Y-m-d', strtotime('-1 day', strtotime($cl_date))); -$next_date = date('Y-m-d', strtotime('+1 day', strtotime($cl_date))); - -// Initialize variables. -$on_behalf_id = $request->getParameter('onBehalfUser', (isset($_SESSION['behalf_id']) ? $_SESSION['behalf_id'] : $user->id)); -$cl_client = $request->getParameter('client', ($request->getMethod()=='POST' ? null : @$_SESSION['client'])); -$_SESSION['client'] = $cl_client; -$cl_project = $request->getParameter('project', ($request->getMethod()=='POST' ? null : @$_SESSION['project'])); -$_SESSION['project'] = $cl_project; -$cl_item_name = $request->getParameter('item_name'); -$cl_cost = $request->getParameter('cost'); - -// Elements of expensesForm. -$form = new Form('expensesForm'); - -if ($user->canManageTeam()) { - $user_list = ttTeamHelper::getActiveUsers(array('putSelfFirst'=>true)); - if (count($user_list) > 1) { - $form->addInput(array('type'=>'combobox', - 'onchange'=>'this.form.submit();', - 'name'=>'onBehalfUser', - 'style'=>'width: 250px;', - 'value'=>$on_behalf_id, - 'data'=>$user_list, - 'datakeys'=>array('id','name'))); - $smarty->assign('on_behalf_control', 1); - } -} - -// Dropdown for clients in MODE_TIME. Use all active clients. -if (MODE_TIME == $user->tracking_mode && $user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillProjectDropdown(this.value);', - 'name'=>'client', - 'style'=>'width: 250px;', - 'value'=>$cl_client, - 'data'=>$active_clients, - 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - // Note: in other modes the client list is filtered to relevant clients only. See below. -} - -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - // Dropdown for projects assigned to user. - $project_list = $user->getAssignedProjects(); - $form->addInput(array('type'=>'combobox', - // 'onchange'=>'fillTaskDropdown(this.value);', - 'name'=>'project', - 'style'=>'width: 250px;', - 'value'=>$cl_project, - 'data'=>$project_list, - 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - - // Dropdown for clients if the clients plugin is enabled. - if ($user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - // We need an array of assigned project ids to do some trimming. - foreach($project_list as $project) - $projects_assigned_to_user[] = $project['id']; - - // Build a client list out of active clients. Use only clients that are relevant to user. - // Also trim their associated project list to only assigned projects (to user). - foreach($active_clients as $client) { - $projects_assigned_to_client = explode(',', $client['projects']); - $intersection = array_intersect($projects_assigned_to_client, $projects_assigned_to_user); - if ($intersection) { - $client['projects'] = implode(',', $intersection); - $client_list[] = $client; - } - } - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillProjectDropdown(this.value);', - 'name'=>'client', - 'style'=>'width: 250px;', - 'value'=>$cl_client, - 'data'=>$client_list, - 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - } -} -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'item_name','style'=>'width: 250px;','value'=>$cl_item_name)); -$form->addInput(array('type'=>'text','maxlength'=>'40','name'=>'cost','style'=>'width: 100px;','value'=>$cl_cost)); -$form->addInput(array('type'=>'calendar','name'=>'date','highlight'=>'expenses','value'=>$cl_date)); // calendar -$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_submit click. -$form->addInput(array('type'=>'submit','name'=>'btn_submit','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.submit'))); - -// Submit. -if ($request->isPost()) { - if ($request->getParameter('btn_submit')) { - // Validate user input. - if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client) - $err->add($i18n->getKey('error.client')); - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $err->add($i18n->getKey('error.project')); - } - if (!ttValidString($cl_item_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.item')); - if (!ttValidFloat($cl_cost)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.cost')); - - // Prohibit creating entries in future. - if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($selected_date->after($browser_today)) - $err->add($i18n->getKey('error.future_date')); - } - // Finished validating input data. - - // Prohibit creating entries in locked range. - if ($user->isDateLocked($selected_date)) - $err->add($i18n->getKey('error.range_locked')); - - // Insert record. - if ($err->no()) { - if (ttExpenseHelper::insert(array('date'=>$cl_date,'user_id'=>$user->getActiveUser(), - 'client_id'=>$cl_client,'project_id'=>$cl_project,'name'=>$cl_item_name,'cost'=>$cl_cost,'status'=>1))) { - header('Location: expenses.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } - } elseif ($request->getParameter('onBehalfUser')) { - if($user->canManageTeam()) { - unset($_SESSION['behalf_id']); - unset($_SESSION['behalf_name']); - - if($on_behalf_id != $user->id) { - $_SESSION['behalf_id'] = $on_behalf_id; - $_SESSION['behalf_name'] = ttUserHelper::getUserName($on_behalf_id); - } - header('Location: expenses.php'); - exit(); - } - } -} - -$smarty->assign('next_date', $next_date); -$smarty->assign('prev_date', $prev_date); -$smarty->assign('day_total', ttExpenseHelper::getTotalForDay($user->getActiveUser(), $cl_date)); -$smarty->assign('expense_items', ttExpenseHelper::getItems($user->getActiveUser(), $cl_date)); -$smarty->assign('client_list', $client_list); -$smarty->assign('project_list', $project_list); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('timestring', $selected_date->toString($user->date_format)); -$smarty->assign('title', $i18n->getKey('title.expenses')); -$smarty->assign('content_page_name', 'mobile/expenses.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/index.php b/mobile/index.php deleted file mode 100644 index 1c7cf231c..000000000 --- a/mobile/index.php +++ /dev/null @@ -1,53 +0,0 @@ -isAuthenticated()) { - if ($user->isAdmin()) { - header('Location: ../admin_teams.php'); - exit(); - } elseif ($user->isClient()) { - header('Location: ../reports.php'); - exit(); - } -} -// Redirect to time.php or mobile/time.php for other roles. -?> - - - - - - diff --git a/mobile/login.php b/mobile/login.php deleted file mode 100644 index a0747deb8..000000000 --- a/mobile/login.php +++ /dev/null @@ -1,94 +0,0 @@ -isPost()) { - $cl_login = $request->getParameter('login'); -} else { - $cl_login = @$_COOKIE['tt_login']; -} -$cl_password = $request->getParameter('password'); - -$form = new Form('loginForm'); -$form->addInput(array('type'=>'text','size'=>'25','maxlength'=>'100','name'=>'login','style'=>'width: 220px;','value'=>$cl_login)); -$form->addInput(array('type'=>'text','size'=>'25','maxlength'=>'50','name'=>'password','style'=>'width: 220px;','aspassword'=>true,'value'=>$cl_password)); -$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_login click. -$form->addInput(array('type'=>'submit','name'=>'btn_login','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.login'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); - if (!ttValidString($cl_password)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - - if ($err->no()) { - - // Use the "limit" plugin if we have one. Ignore include errors. - // The "limit" plugin is not required for normal operation of the Time Tracker. - @include('../plugins/limit/access_check.php'); - - if ($auth->doLogin($cl_login, $cl_password)) { - - // Set current user date (as determined by user browser) into session. - $current_user_date = $request->getParameter('browser_today', null); - if ($current_user_date) - $_SESSION['date'] = $current_user_date; - - // Remember user login in a cookie. - setcookie('tt_login', $cl_login, time() + COOKIE_EXPIRE, '/'); - - $user = new ttUser(null, $auth->getUserId()); - // Redirect, depending on user role. - if ($user->isAdmin()) { - header('Location: ../admin_teams.php'); - } elseif ($user->isClient()) { - header('Location: ../reports.php'); - } else { - header('Location: time.php'); - } - exit(); - } else - $err->add($i18n->getKey('error.auth')); - } -} // isPost - -if(!isTrue(MULTITEAM_MODE) && !ttTeamHelper::getTeams()) - $err->add($i18n->getKey('error.no_teams')); - -// Determine whether to show login hint. It is currently used only for Windows LDAP authentication. -$show_hint = ('ad' == $GLOBALS['AUTH_MODULE_PARAMS']['type']); - -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('show_hint', $show_hint); -$smarty->assign('onload', 'onLoad="document.loginForm.'.(!$cl_login?'login':'password').'.focus()"'); -$smarty->assign('title', $i18n->getKey('title.login')); -$smarty->assign('content_page_name', 'mobile/login.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/project_add.php b/mobile/project_add.php deleted file mode 100644 index 1ae6b4dfa..000000000 --- a/mobile/project_add.php +++ /dev/null @@ -1,95 +0,0 @@ -tracking_mode && MODE_PROJECTS_AND_TASKS != $user->tracking_mode)) { - header('Location: access_denied.php'); - exit(); -} - -$users = ttTeamHelper::getActiveUsers(); -foreach ($users as $user_item) - $all_users[$user_item['id']] = $user_item['name']; - -$tasks = ttTeamHelper::getActiveTasks($user->team_id); -foreach ($tasks as $task_item) - $all_tasks[$task_item['id']] = $task_item['name']; - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('project_name')); - $cl_description = trim($request->getParameter('description')); - $cl_users = $request->getParameter('users', array()); - $cl_tasks = $request->getParameter('tasks', array()); -} else { - foreach ($users as $user_item) - $cl_users[] = $user_item['id']; - foreach ($tasks as $task_item) - $cl_tasks[] = $task_item['id']; -} - -$form = new Form('projectForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'project_name','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'description','class'=>'mobile-textarea','value'=>$cl_description)); -$form->addInput(array('type'=>'checkboxgroup','name'=>'users','data'=>$all_users,'layout'=>'H','value'=>$cl_users)); -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) - $form->addInput(array('type'=>'checkboxgroup','name'=>'tasks','data'=>$all_tasks,'layout'=>'H','value'=>$cl_tasks)); -$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description')); - - if ($err->no()) { - if (!ttProjectHelper::getProjectByName($cl_name)) { - if (ttProjectHelper::insert(array( - 'team_id' => $user->team_id, - 'name' => $cl_name, - 'description' => $cl_description, - 'users' => $cl_users, - 'tasks' => $cl_tasks, - 'status' => ACTIVE))) { - header('Location: projects.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.project_exists')); - } -} // isPost - -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.projectForm.project_name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.add_project')); -$smarty->assign('content_page_name', 'mobile/project_add.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/project_delete.php b/mobile/project_delete.php deleted file mode 100644 index eb5e04039..000000000 --- a/mobile/project_delete.php +++ /dev/null @@ -1,69 +0,0 @@ -tracking_mode && MODE_PROJECTS_AND_TASKS != $user->tracking_mode)) { - header('Location: access_denied.php'); - exit(); -} - -$cl_project_id = (int)$request->getParameter('id'); -$project = ttProjectHelper::get($cl_project_id); -$project_to_delete = $project['name']; - -$form = new Form('projectDeleteForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_project_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); - -if ($request->isPost()) { - if ($request->getParameter('btn_delete')) { - if(ttProjectHelper::get($cl_project_id)) { - if (ttProjectHelper::delete($cl_project_id)) { - header('Location: projects.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.db')); - } elseif ($request->getParameter('btn_cancel')) { - header('Location: projects.php'); - exit(); - } -} // isPost - -$smarty->assign('project_to_delete', $project_to_delete); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.projectDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->getKey('title.delete_project')); -$smarty->assign('content_page_name', 'mobile/project_delete.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/project_edit.php b/mobile/project_edit.php deleted file mode 100644 index f7a37aaf5..000000000 --- a/mobile/project_edit.php +++ /dev/null @@ -1,139 +0,0 @@ -tracking_mode && MODE_PROJECTS_AND_TASKS != $user->tracking_mode)) { - header('Location: access_denied.php'); - exit(); -} - -$cl_project_id = (int)$request->getParameter('id'); - -$users = ttTeamHelper::getActiveUsers(); -foreach ($users as $user_item) - $all_users[$user_item['id']] = $user_item['name']; - -$tasks = ttTeamHelper::getActiveTasks($user->team_id); -foreach ($tasks as $task_item) - $all_tasks[$task_item['id']] = $task_item['name']; - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('project_name')); - $cl_description = trim($request->getParameter('description')); - $cl_status = $request->getParameter('status'); - $cl_users = $request->getParameter('users', array()); - $cl_tasks = $request->getParameter('tasks', array()); -} else { - $project = ttProjectHelper::get($cl_project_id); - $cl_name = $project['name']; - $cl_description = $project['description']; - $cl_status = $project['status']; - - $mdb2 = getConnection(); - $sql = "select user_id from tt_user_project_binds where status = 1 and project_id = $cl_project_id"; - $res = $mdb2->query($sql); - if (is_a($res, 'PEAR_Error')) - die($res->getMessage()); - while ($row = $res->fetchRow()) - $cl_users[] = $row['user_id']; - - $cl_tasks = explode(',', $project['tasks']); -} - -$form = new Form('projectForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_project_id)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'project_name','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'description','class'=>'mobile-textarea','value'=>$cl_description)); -$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status, - 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive')))); -$form->addInput(array('type'=>'checkboxgroup','name'=>'users','data'=>$all_users,'layout'=>'H','value'=>$cl_users)); -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) - $form->addInput(array('type'=>'checkboxgroup','name'=>'tasks','data'=>$all_tasks,'layout'=>'H','value'=>$cl_tasks)); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->getKey('button.copy'))); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description')); - - if ($err->no()) { - if ($request->getParameter('btn_save')) { - $existing_project = ttProjectHelper::getProjectByName($cl_name); - if (!$existing_project || ($cl_project_id == $existing_project['id'])) { - // Update project information. - if (ttProjectHelper::update(array( - 'id' => $cl_project_id, - 'name' => $cl_name, - 'description' => $cl_description, - 'status' => $cl_status, - 'users' => $cl_users, - 'tasks' => $cl_tasks))) { - header('Location: projects.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.project_exists')); - } - - if ($request->getParameter('btn_copy')) { - if (!ttProjectHelper::getProjectByName($cl_name)) { - if (ttProjectHelper::insert(array( - 'team_id' => $user->team_id, - 'name' => $cl_name, - 'description' => $cl_description, - 'users' => $cl_users, - 'tasks' => $cl_tasks, - 'status' => ACTIVE))) { - header('Location: projects.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.project_exists')); - } - - if ($request->getParameter('btn_delete')) { - header("Location: project_delete.php?id=$cl_project_id"); - exit(); - } - } -} // isPost - -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.projectForm.project_name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.edit_project')); -$smarty->assign('content_page_name', 'mobile/project_edit.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/projects.php b/mobile/projects.php deleted file mode 100644 index 5dee3605d..000000000 --- a/mobile/projects.php +++ /dev/null @@ -1,49 +0,0 @@ -tracking_mode && MODE_PROJECTS_AND_TASKS != $user->tracking_mode)) { - header('Location: access_denied.php'); - exit(); -} - -if($user->canManageTeam()) { - $active_projects = ttTeamHelper::getActiveProjects($user->team_id); - $inactive_projects = ttTeamHelper::getInactiveProjects($user->team_id); -} else - $active_projects = $user->getAssignedProjects(); - -$smarty->assign('active_projects', $active_projects); -$smarty->assign('inactive_projects', $inactive_projects); -$smarty->assign('title', $i18n->getKey('title.projects')); -$smarty->assign('content_page_name', 'mobile/projects.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/task_add.php b/mobile/task_add.php deleted file mode 100644 index 700b91584..000000000 --- a/mobile/task_add.php +++ /dev/null @@ -1,84 +0,0 @@ -tracking_mode) { - header('Location: access_denied.php'); - exit(); -} - -$projects = ttTeamHelper::getActiveProjects($user->team_id); - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('name')); - $cl_description = trim($request->getParameter('description')); - $cl_projects = $request->getParameter('projects'); -} else { - foreach ($projects as $project_item) - $cl_projects[] = $project_item['id']; -} - -$form = new Form('taskForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'description','class'=>'mobile-textarea','value'=>$cl_description)); -$form->addInput(array('type'=>'checkboxgroup','name'=>'projects','layout'=>'H','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_projects)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.add'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description')); - - if ($err->no()) { - if (!ttTaskHelper::getTaskByName($cl_name)) { - if (ttTaskHelper::insert(array( - 'team_id' => $user->team_id, - 'name' => $cl_name, - 'description' => $cl_description, - 'status' => ACTIVE, - 'projects' => $cl_projects))) { - header('Location: tasks.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.task_exists')); - } -} // isPost - -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.taskForm.name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.add_task')); -$smarty->assign('content_page_name', 'mobile/task_add.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/task_delete.php b/mobile/task_delete.php deleted file mode 100644 index 1fea8e127..000000000 --- a/mobile/task_delete.php +++ /dev/null @@ -1,69 +0,0 @@ -tracking_mode) { - header('Location: access_denied.php'); - exit(); -} - -$cl_task_id = (int)$request->getParameter('id'); -$task = ttTaskHelper::getTask($cl_task_id); -$task_to_delete = $task['name']; - -$form = new Form('taskDeleteForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_task_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); - -if ($request->isPost()) { - if ($request->getParameter('btn_delete')) { - if(ttTaskHelper::getTask($cl_task_id)) { - if (ttTaskHelper::delete($cl_task_id)) { - header('Location: tasks.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.db')); - } elseif ($request->getParameter('btn_cancel')) { - header('Location: tasks.php'); - exit(); - } -} // isPost - -$smarty->assign('task_to_delete', $task_to_delete); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.taskDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->getKey('title.delete_task')); -$smarty->assign('content_page_name', 'mobile/task_delete.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/task_edit.php b/mobile/task_edit.php deleted file mode 100644 index e2dcc9908..000000000 --- a/mobile/task_edit.php +++ /dev/null @@ -1,121 +0,0 @@ -tracking_mode) { - header('Location: access_denied.php'); - exit(); -} - -$cl_task_id = (int)$request->getParameter('id'); -$projects = ttTeamHelper::getActiveProjects($user->team_id); - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('name')); - $cl_description = trim($request->getParameter('description')); - $cl_status = $request->getParameter('status'); - $cl_projects = $request->getParameter('projects'); -} else { - $task = ttTaskHelper::getTask($cl_task_id); - $cl_name = $task['name']; - $cl_description = $task['description']; - $cl_status = $task['status']; - - $assigned_projects = ttTaskHelper::getAssignedProjects($cl_task_id); - foreach ($assigned_projects as $project_item) - $cl_projects[] = $project_item['id']; -} - -$form = new Form('taskForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_task_id)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'description','class'=>'mobile-textarea','value'=>$cl_description)); -$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status, - 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive')))); -$form->addInput(array('type'=>'checkboxgroup','name'=>'projects','layout'=>'H','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_projects)); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->getKey('button.copy'))); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); - - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description')); - - if ($err->no()) { - if ($request->getParameter('btn_save')) { - $existing_task = ttTaskHelper::getTaskByName($cl_name); - if (!$existing_task || ($cl_task_id == $existing_task['id'])) { - // Update task information. - if (ttTaskHelper::update(array( - 'task_id' => $cl_task_id, - 'name' => $cl_name, - 'description' => $cl_description, - 'status' => $cl_status, - 'projects' => $cl_projects))) { - header('Location: tasks.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.task_exists')); - } - - if ($request->getParameter('btn_copy')) { - if (!ttTaskHelper::getTaskByName($cl_name)) { - if (ttTaskHelper::insert(array( - 'team_id' => $user->team_id, - 'name' => $cl_name, - 'description' => $cl_description, - 'status' => $cl_status, - 'projects' => $cl_projects))) { - header('Location: tasks.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.task_exists')); - } - - if ($request->getParameter('btn_delete')) { - header("Location: task_delete.php?id=$cl_task_id"); - exit(); - } - } -} // isPost - -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.edit_task')); -$smarty->assign('content_page_name', 'mobile/task_edit.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/time.php b/mobile/time.php deleted file mode 100644 index b0c5de11a..000000000 --- a/mobile/time.php +++ /dev/null @@ -1,290 +0,0 @@ -getParameter('date', @$_SESSION['date']); -$selected_date = new DateAndTime(DB_DATEFORMAT, $cl_date); -if($selected_date->isError()) - $selected_date = new DateAndTime(DB_DATEFORMAT); -if(!$cl_date) - $cl_date = $selected_date->toString(DB_DATEFORMAT); -$_SESSION['date'] = $cl_date; - -// Determine previous and next dates for simple navigation. -$prev_date = date('Y-m-d', strtotime('-1 day', strtotime($cl_date))); -$next_date = date('Y-m-d', strtotime('+1 day', strtotime($cl_date))); - -// Use custom fields plugin if it is enabled. -if ($user->isPluginEnabled('cf')) { - require_once('../plugins/CustomFields.class.php'); - $custom_fields = new CustomFields($user->team_id); - $smarty->assign('custom_fields', $custom_fields); -} - -// Initialize variables. -$cl_start = trim($request->getParameter('start')); -$cl_finish = trim($request->getParameter('finish')); -$cl_duration = trim($request->getParameter('duration')); -$cl_note = trim($request->getParameter('note')); -// Custom field. -$cl_cf_1 = trim($request->getParameter('cf_1', ($request->getMethod()=='POST'? null : @$_SESSION['cf_1']))); -$_SESSION['cf_1'] = $cl_cf_1; -$cl_billable = 1; -if ($user->isPluginEnabled('iv')) { - if ($request->isPost()) { - $cl_billable = $request->getParameter('billable'); - $_SESSION['billable'] = (int) $cl_billable; - } else - if (isset($_SESSION['billable'])) - $cl_billable = $_SESSION['billable']; -} -$cl_client = $request->getParameter('client', ($request->getMethod()=='POST'? null : @$_SESSION['client'])); -$_SESSION['client'] = $cl_client; -$cl_project = $request->getParameter('project', ($request->getMethod()=='POST'? null : @$_SESSION['project'])); -$_SESSION['project'] = $cl_project; -$cl_task = $request->getParameter('task', ($request->getMethod()=='POST'? null : @$_SESSION['task'])); -$_SESSION['task'] = $cl_task; - -// Elements of timeRecordForm. -$form = new Form('timeRecordForm'); - -// Dropdown for clients in MODE_TIME. Use all active clients. -if (MODE_TIME == $user->tracking_mode && $user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillProjectDropdown(this.value);', - 'name'=>'client', - 'style'=>'width: 250px;', - 'value'=>$cl_client, - 'data'=>$active_clients, - 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - // Note: in other modes the client list is filtered to relevant clients only. See below. -} - -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - // Dropdown for projects assigned to user. - $project_list = $user->getAssignedProjects(); - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillTaskDropdown(this.value);', - 'name'=>'project', - 'style'=>'width: 250px;', - 'value'=>$cl_project, - 'data'=>$project_list, - 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - - // Dropdown for clients if the clients plugin is enabled. - if ($user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - // We need an array of assigned project ids to do some trimming. - foreach($project_list as $project) - $projects_assigned_to_user[] = $project['id']; - - // Build a client list out of active clients. Use only clients that are relevant to user. - // Also trim their associated project list to only assigned projects (to user). - foreach($active_clients as $client) { - $projects_assigned_to_client = explode(',', $client['projects']); - if (is_array($projects_assigned_to_client) && is_array($projects_assigned_to_user)) - $intersection = array_intersect($projects_assigned_to_client, $projects_assigned_to_user); - if ($intersection) { - $client['projects'] = implode(',', $intersection); - $client_list[] = $client; - } - } - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillProjectDropdown(this.value);', - 'name'=>'client', - 'style'=>'width: 250px;', - 'value'=>$cl_client, - 'data'=>$client_list, - 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - } -} - -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - $task_list = ttTeamHelper::getActiveTasks($user->team_id); - $form->addInput(array('type'=>'combobox', - 'name'=>'task', - 'style'=>'width: 250px;', - 'value'=>$cl_task, - 'data'=>$task_list, - 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); -} -if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) { - $form->addInput(array('type'=>'text','name'=>'start','value'=>$cl_start,'onchange'=>"formDisable('start');")); - $form->addInput(array('type'=>'text','name'=>'finish','value'=>$cl_finish,'onchange'=>"formDisable('finish');")); -} -if (!$user->canManageTeam() && defined('READONLY_START_FINISH') && isTrue(READONLY_START_FINISH)) { - // Make the start and finish fields read-only. - $form->getElement('start')->setEnable(false); - $form->getElement('finish')->setEnable(false); -} -if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type)) - $form->addInput(array('type'=>'text','name'=>'duration','value'=>$cl_duration,'onchange'=>"formDisable('duration');")); -$form->addInput(array('type'=>'textarea','name'=>'note','style'=>'width: 250px; height: 60px;','value'=>$cl_note)); -if ($user->isPluginEnabled('iv')) - $form->addInput(array('type'=>'checkbox','name'=>'billable','data'=>1,'value'=>$cl_billable)); -$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_submit click. -$form->addInput(array('type'=>'submit','name'=>'btn_submit','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.submit'))); - -// If we have custom fields - add controls for them. -if ($custom_fields && $custom_fields->fields[0]) { - // Only one custom field is supported at this time. - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) { - $form->addInput(array('type'=>'text','name'=>'cf_1','value'=>$cl_cf_1)); - } elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) { - $form->addInput(array('type'=>'combobox','name'=>'cf_1', - 'style'=>'width: 250px;', - 'value'=>$cl_cf_1, - 'data'=>$custom_fields->options, - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - } -} - -// Submit. -if ($request->isPost()) { - if ($request->getParameter('btn_submit')) { - - // Validate user input. - if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client) - $err->add($i18n->getKey('error.client')); - if ($custom_fields) { - if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $err->add($i18n->getKey('error.field'), $custom_fields->fields[0]['label']); - } - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $err->add($i18n->getKey('error.project')); - } - if (strlen($cl_duration) == 0) { - if ($cl_start || $cl_finish) { - if (!ttTimeHelper::isValidTime($cl_start)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.start')); - if ($cl_finish) { - if (!ttTimeHelper::isValidTime($cl_finish)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.finish')); - if (!ttTimeHelper::isValidInterval($cl_start, $cl_finish)) - $err->add($i18n->getKey('error.interval'), $i18n->getKey('label.finish'), $i18n->getKey('label.start')); - } - } else { - if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) { - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.start')); - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish')); - } - if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type)) - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration')); - } - } else { - if (!ttTimeHelper::isValidDuration($cl_duration)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); - } - if (!ttValidString($cl_note, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.note')); - // Finished validating user input. - - // Prohibit creating entries in future. - if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($selected_date->after($browser_today)) - $err->add($i18n->getKey('error.future_date')); - } - - // Prohibit creating entries in locked range. - if ($user->isDateLocked($selected_date)) - $err->add($i18n->getKey('error.range_locked')); - - // Prohibit creating another uncompleted record. - if ($err->no()) { - if (($not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser())) && (($cl_finish == '') && ($cl_duration == ''))) - $err->add($i18n->getKey('error.uncompleted_exists')." ".$i18n->getKey('error.goto_uncompleted').""); - } - - // Prohibit creating an overlapping record. - if ($err->no()) { - if (ttTimeHelper::overlaps($user->getActiveUser(), $cl_date, $cl_start, $cl_finish)) - $err->add($i18n->getKey('error.overlap')); - } - - if ($err->no()) { - $id = ttTimeHelper::insert(array( - 'date' => $cl_date, - 'user_id' => $user->getActiveUser(), - 'client' => $cl_client, - 'project' => $cl_project, - 'task' => $cl_task, - 'start' => $cl_start, - 'finish' => $cl_finish, - 'duration' => $cl_duration, - 'note' => $cl_note, - 'billable' => $cl_billable)); - - // Insert a custom field if we have it. - $result = true; - if ($id && $custom_fields && $cl_cf_1) { - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) - $result = $custom_fields->insert($id, $custom_fields->fields[0]['id'], null, $cl_cf_1); - elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) - $result = $custom_fields->insert($id, $custom_fields->fields[0]['id'], $cl_cf_1, null); - } - - if ($id && $result) { - header('Location: time.php'); - exit(); - } - $err->add($i18n->getKey('error.db')); - } - } -} // isPost - -$smarty->assign('next_date', $next_date); -$smarty->assign('prev_date', $prev_date); -$smarty->assign('time_records', ttTimeHelper::getRecords($user->getActiveUser(), $cl_date)); -$smarty->assign('day_total', ttTimeHelper::getTimeForDay($user->getActiveUser(), $cl_date)); -$smarty->assign('client_list', $client_list); -$smarty->assign('project_list', $project_list); -$smarty->assign('task_list', $task_list); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="fillDropdowns()"'); -$smarty->assign('timestring', $selected_date->toString($user->date_format)); -$smarty->assign('title', $i18n->getKey('title.time')); -$smarty->assign('content_page_name', 'mobile/time.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/time_delete.php b/mobile/time_delete.php deleted file mode 100644 index c9602b4e7..000000000 --- a/mobile/time_delete.php +++ /dev/null @@ -1,89 +0,0 @@ -getParameter('id'); -$time_rec = ttTimeHelper::getRecord($cl_id, $user->getActiveUser()); - -// Prohibit deleting invoiced records. -if ($time_rec['invoice_id']) die($i18n->getKey('error.sys')); - -// Escape comment for presentation. -$time_rec['comment'] = htmlspecialchars($time_rec['comment']); - -if ($request->isPost()) { - if ($request->getParameter('delete_button')) { // Delete button pressed. - - // Determine if it's okay to delete the record. - $item_date = new DateAndTime(DB_DATEFORMAT, $time_rec['date']); - - // Determine if the record is uncompleted. - $uncompleted = ($time_rec['duration'] == '0:00'); - - if ($user->isDateLocked($item_date) && !$uncompleted) - $err->add($i18n->getKey('error.range_locked')); - - if ($err->no()) { - - // Delete the record. - $result = ttTimeHelper::delete($cl_id, $user->getActiveUser()); - - if ($result) { - header('Location: time.php'); - exit(); - } else { - $err->add($i18n->getKey('error.db')); - } - } - } - if ($request->getParameter('cancel_button')) { // Cancel button pressed. - header('Location: time.php'); - exit(); - } -} // isPost - -$form = new Form('timeRecordForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); -$form->addInput(array('type'=>'submit','name'=>'delete_button','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'cancel_button','value'=>$i18n->getKey('button.cancel'))); -$smarty->assign('time_rec', $time_rec); -$smarty->assign('forms', array($form->getName() => $form->toArray())); -$smarty->assign('title', $i18n->getKey('title.delete_time_record')); -$smarty->assign('content_page_name', 'mobile/time_delete.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/time_edit.php b/mobile/time_edit.php deleted file mode 100644 index 9e1fc324b..000000000 --- a/mobile/time_edit.php +++ /dev/null @@ -1,336 +0,0 @@ -isPluginEnabled('cf')) { - require_once('../plugins/CustomFields.class.php'); - $custom_fields = new CustomFields($user->team_id); - $smarty->assign('custom_fields', $custom_fields); -} - -$cl_id = $request->getParameter('id'); - -// Get the time record we are editing. -$time_rec = ttTimeHelper::getRecord($cl_id, $user->getActiveUser()); - -// Prohibit editing invoiced records. -if ($time_rec['invoice_id']) die($i18n->getKey('error.sys')); - -$item_date = new DateAndTime(DB_DATEFORMAT, $time_rec['date']); - -// Initialize variables. -$cl_start = $cl_finish = $cl_duration = $cl_date = $cl_note = $cl_project = $cl_task = $cl_billable = null; -if ($request->isPost()) { - $cl_start = trim($request->getParameter('start')); - $cl_finish = trim($request->getParameter('finish')); - $cl_duration = trim($request->getParameter('duration')); - $cl_date = $request->getParameter('date'); - $cl_note = trim($request->getParameter('note')); - $cl_cf_1 = trim($request->getParameter('cf_1')); - $cl_client = $request->getParameter('client'); - $cl_project = $request->getParameter('project'); - $cl_task = $request->getParameter('task'); - $cl_billable = 1; - if ($user->isPluginEnabled('iv')) - $cl_billable = $request->getParameter('billable'); -} else { - $cl_client = $time_rec['client_id']; - $cl_project = $time_rec['project_id']; - $cl_task = $time_rec['task_id']; - $cl_start = $time_rec['start']; - $cl_finish = $time_rec['finish']; - $cl_duration = $time_rec['duration']; - $cl_date = $item_date->toString($user->date_format); - $cl_note = $time_rec['comment']; - - // If we have custom fields - obtain values for them. - if ($custom_fields) { - // Get custom field value for time record. - $fields = $custom_fields->get($time_rec['id']); - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) - $cl_cf_1 = $fields[0]['value']; - elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) - $cl_cf_1 = $fields[0]['option_id']; - } - - $cl_billable = $time_rec['billable']; - - // Add an info message to the form if we are editing an uncompleted record. - if (strlen($cl_start) > 0 && $cl_start == $cl_finish && $cl_duration == '0:00') { - $cl_finish = ''; - $cl_duration = ''; - $msg->add($i18n->getKey('form.time_edit.uncompleted')); - } -} - -// Initialize elements of 'timeRecordForm'. -$form = new Form('timeRecordForm'); - -// Dropdown for clients in MODE_TIME. Use all active clients. -if (MODE_TIME == $user->tracking_mode && $user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillProjectDropdown(this.value);', - 'name'=>'client', - 'style'=>'width: 250px;', - 'value'=>$cl_client, - 'data'=>$active_clients, - 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - // Note: in other modes the client list is filtered to relevant clients only. See below. -} - -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - // Dropdown for projects assigned to user. - $project_list = $user->getAssignedProjects(); - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillTaskDropdown(this.value);', - 'name'=>'project', - 'style'=>'width: 250px;', - 'value'=>$cl_project, - 'data'=>$project_list, - 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - - // Dropdown for clients if the clients plugin is enabled. - if ($user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - // We need an array of assigned project ids to do some trimming. - foreach($project_list as $project) - $projects_assigned_to_user[] = $project['id']; - - // Build a client list out of active clients. Use only clients that are relevant to user. - // Also trim their associated project list to only assigned projects (to user). - foreach($active_clients as $client) { - $projects_assigned_to_client = explode(',', $client['projects']); - if (is_array($projects_assigned_to_client) && is_array($projects_assigned_to_user)) - $intersection = array_intersect($projects_assigned_to_client, $projects_assigned_to_user); - if ($intersection) { - $client['projects'] = implode(',', $intersection); - $client_list[] = $client; - } - } - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillProjectDropdown(this.value);', - 'name'=>'client', - 'style'=>'width: 250px;', - 'value'=>$cl_client, - 'data'=>$client_list, - 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - } -} - -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - $task_list = ttTeamHelper::getActiveTasks($user->team_id); - $form->addInput(array('type'=>'combobox', - 'name'=>'task', - 'style'=>'width: 250px;', - 'value'=>$cl_task, - 'data'=>$task_list, - 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); -} - -// Add other controls. -if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) { - $form->addInput(array('type'=>'text','name'=>'start','value'=>$cl_start,'onchange'=>"formDisable('start');")); - $form->addInput(array('type'=>'text','name'=>'finish','value'=>$cl_finish,'onchange'=>"formDisable('finish');")); -} -if (!$user->canManageTeam() && defined('READONLY_START_FINISH') && isTrue(READONLY_START_FINISH)) { - // Make the start and finish fields read-only. - $form->getElement('start')->setEnable(false); - $form->getElement('finish')->setEnable(false); -} -if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type)) - $form->addInput(array('type'=>'text','name'=>'duration','value'=>$cl_duration,'onchange'=>"formDisable('duration');")); -$form->addInput(array('type'=>'datefield','name'=>'date','maxlength'=>'20','value'=>$cl_date)); -$form->addInput(array('type'=>'textarea','name'=>'note','class'=>'mobile-textarea','value'=>$cl_note)); -// If we have custom fields - add controls for them. -if ($custom_fields && $custom_fields->fields[0]) { - // Only one custom field is supported at this time. - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) { - $form->addInput(array('type'=>'text','name'=>'cf_1','value'=>$cl_cf_1)); - } elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) { - $form->addInput(array('type'=>'combobox', - 'name'=>'cf_1', - 'style'=>'width: 250px;', - 'value'=>$cl_cf_1, - 'data'=>$custom_fields->options, - 'empty' => array('' => $i18n->getKey('dropdown.select')))); - } -} -// Hidden control for record id. -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); -if ($user->isPluginEnabled('iv')) - $form->addInput(array('type'=>'checkbox','name'=>'billable','data'=>1,'value'=>$cl_billable)); -$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_save click. -$form->addInput(array('type'=>'submit','name'=>'btn_save','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); - -if ($request->isPost()) { - - // Validate user input. - if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client) - $err->add($i18n->getKey('error.client')); - if ($custom_fields) { - if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $err->add($i18n->getKey('error.field'), $custom_fields->fields[0]['label']); - } - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $err->add($i18n->getKey('error.project')); - } - if (!$cl_duration) { - if ('0' == $cl_duration) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); - elseif ($cl_start || $cl_finish) { - if (!ttTimeHelper::isValidTime($cl_start)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.start')); - if ($cl_finish) { - if (!ttTimeHelper::isValidTime($cl_finish)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.finish')); - if (!ttTimeHelper::isValidInterval($cl_start, $cl_finish)) - $err->add($i18n->getKey('error.interval'), $i18n->getKey('label.finish'), $i18n->getKey('label.start')); - } - } else { - if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) { - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.start')); - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish')); - } - if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type)) - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration')); - } - } else { - if (!ttTimeHelper::isValidDuration($cl_duration)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); - } - if (!ttValidDate($cl_date)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.date')); - if (!ttValidString($cl_note, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.note')); - // Finished validating user input. - - // This is a new date for the time record. - $new_date = new DateAndTime($user->date_format, $cl_date); - - // Prohibit creating entries in future. - if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($new_date->after($browser_today)) - $err->add($i18n->getKey('error.future_date')); - } - - // Save record. - if ($request->getParameter('btn_save')) { - // We need to: - // 1) Prohibit saving locked entries in any form. - // 2) Prohibit saving completed unlocked entries into locked range. - // 3) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists. - - // Now, step by step. - if ($err->no()) { - // 1) Prohibit saving locked entries in any form. - if ($user->isDateLocked($item_date)) - $err->add($i18n->getKey('error.range_locked')); - - // 2) Prohibit saving completed unlocked entries into locked range. - if ($err->no() && $user->isDateLocked($new_date)) - $err->add($i18n->getKey('error.range_locked')); - - // 3) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists. - $uncompleted = ($cl_finish == '' && $cl_duration == ''); - if ($uncompleted) { - $not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser()); - if ($not_completed_rec && ($time_rec['id'] <> $not_completed_rec['id'])) { - // We have another not completed record. - $err->add($i18n->getKey('error.uncompleted_exists')." ".$i18n->getKey('error.goto_uncompleted').""); - } - } - } - - // Prohibit creating an overlapping record. - if ($err->no()) { - if (ttTimeHelper::overlaps($user->getActiveUser(), $new_date->toString(DB_DATEFORMAT), $cl_start, $cl_finish, $cl_id)) - $err->add($i18n->getKey('error.overlap')); - } - - // Now, an update. - if ($err->no()) { - $res = ttTimeHelper::update(array( - 'id'=>$cl_id, - 'date'=>$new_date->toString(DB_DATEFORMAT), - 'user_id'=>$user->getActiveUser(), - 'client'=>$cl_client, - 'project'=>$cl_project, - 'task'=>$cl_task, - 'start'=>$cl_start, - 'finish'=>$cl_finish, - 'duration'=>$cl_duration, - 'note'=>$cl_note, - 'billable'=>$cl_billable)); - - // If we have custom fields - update values. - if ($res && $custom_fields) { - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) - $res = $custom_fields->update($cl_id, $custom_fields->fields[0]['id'], null, $cl_cf_1); - elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) - $res = $custom_fields->update($cl_id, $custom_fields->fields[0]['id'], $cl_cf_1, null); - } - if ($res) - { - header('Location: time.php?date='.$new_date->toString(DB_DATEFORMAT)); - exit(); - } - } - } - - if ($request->getParameter('btn_delete')) { - header("Location: time_delete.php?id=$cl_id"); - exit(); - } -} // isPost - -$smarty->assign('client_list', $client_list); -$smarty->assign('project_list', $project_list); -$smarty->assign('task_list', $task_list); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="fillDropdowns()"'); -$smarty->assign('title', $i18n->getKey('title.edit_time_record')); -$smarty->assign('content_page_name', 'mobile/time_edit.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/timer.php b/mobile/timer.php deleted file mode 100644 index 8a82bb6d9..000000000 --- a/mobile/timer.php +++ /dev/null @@ -1,301 +0,0 @@ -getParameter('date', @$_SESSION['date']); -$selected_date = new DateAndTime(DB_DATEFORMAT, $cl_date); -if($selected_date->isError()) - $selected_date = new DateAndTime(DB_DATEFORMAT); -if(!$cl_date) - $cl_date = $selected_date->toString(DB_DATEFORMAT); -$_SESSION['date'] = $cl_date; -// TODO: for time page we may limit the day to today only. - -// Use custom fields plugin if it is enabled. -if ($user->isPluginEnabled('cf')) { - require_once('../plugins/CustomFields.class.php'); - $custom_fields = new CustomFields($user->team_id); - $smarty->assign('custom_fields', $custom_fields); -} - -// Initialize variables. -$cl_start = trim($request->getParameter('browser_time')); -$cl_finish = trim($request->getParameter('browser_time')); -// Custom field. -$cl_cf_1 = trim($request->getParameter('cf_1', ($request->getMethod()=='POST'? null : @$_SESSION['cf_1']))); -$_SESSION['cf_1'] = $cl_cf_1; -$cl_billable = 1; -if ($user->isPluginEnabled('iv')) { - if ($request->isPost()) { - $cl_billable = $request->getParameter('billable'); - $_SESSION['billable'] = (int) $cl_billable; - } else - if (isset($_SESSION['billable'])) - $cl_billable = $_SESSION['billable']; -} -$cl_client = $request->getParameter('client', @$_SESSION['client']); -$_SESSION['client'] = $cl_client; -$cl_project = $request->getParameter('project', @$_SESSION['project']); -$_SESSION['project'] = $cl_project; -$cl_task = $request->getParameter('task', @$_SESSION['task']); -$_SESSION['task'] = $cl_task; - -// Obtain uncompleted record. Assumtion is that only 1 uncompleted record is allowed. -$uncompleted = ttTimeHelper::getUncompleted($user->getActiveUser()); -$enable_controls = ($uncompleted == null); - -// Elements of timeRecordForm. -$form = new Form('timeRecordForm'); - -// Dropdown for clients in MODE_TIME. Use all active clients. -if (MODE_TIME == $user->tracking_mode && $user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillProjectDropdown(this.value);', - 'name'=>'client', - 'style'=>'width: 250px;', - 'enable'=>$enable_controls, - 'value'=>$cl_client, - 'data'=>$active_clients, - 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - // Note: in other modes the client list is filtered to relevant clients only. See below. -} - -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - // Dropdown for projects assigned to user. - $project_list = $user->getAssignedProjects(); - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillTaskDropdown(this.value);', - 'name'=>'project', - 'style'=>'width: 250px;', - 'enable'=>$enable_controls, - 'value'=>$cl_project, - 'data'=>$project_list, - 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - - // Dropdown for clients if the clients plugin is enabled. - if ($user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - // We need an array of assigned project ids to do some trimming. - foreach($project_list as $project) - $projects_assigned_to_user[] = $project['id']; - - // Build a client list out of active clients. Use only clients that are relevant to user. - // Also trim their associated project list to only assigned projects (to user). - foreach($active_clients as $client) { - $projects_assigned_to_client = explode(',', $client['projects']); - $intersection = array_intersect($projects_assigned_to_client, $projects_assigned_to_user); - if ($intersection) { - $client['projects'] = implode(',', $intersection); - $client_list[] = $client; - } - } - $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillProjectDropdown(this.value);', - 'name'=>'client', - 'style'=>'width: 250px;', - 'enable'=>$enable_controls, - 'value'=>$cl_client, - 'data'=>$client_list, - 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - } -} - -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - $task_list = ttTeamHelper::getActiveTasks($user->team_id); - $form->addInput(array('type'=>'combobox', - 'name'=>'task', - 'style'=>'width: 250px;', - 'enable'=>$enable_controls, - 'value'=>$cl_task, - 'data'=>$task_list, - 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); -} -if ($user->isPluginEnabled('iv')) - $form->addInput(array('type'=>'checkbox','name'=>'billable','data'=>1,'value'=>$cl_billable,'enable'=>$enable_controls)); -$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on button click. -$form->addInput(array('type'=>'hidden','name'=>'browser_time','value'=>'')); // User current time, which gets filled in on button click. -$enable_start = $uncompleted ? false : true; -if (!$uncompleted) - $form->addInput(array('type'=>'submit','name'=>'btn_start','onclick'=>'browser_time.value=get_time()','value'=>$i18n->getKey('label.start'),'enable'=>$enable_start)); -else - $form->addInput(array('type'=>'submit','name'=>'btn_stop','onclick'=>'browser_time.value=get_time()','value'=>$i18n->getKey('label.finish'),'enable'=>!$enable_start)); - -// If we have custom fields - add controls for them. -if ($custom_fields && $custom_fields->fields[0]) { - // Only one custom field is supported at this time. - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) { - $form->addInput(array('type'=>'text','name'=>'cf_1','value'=>$cl_cf_1)); - } elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) { - $form->addInput(array('type'=>'combobox','name'=>'cf_1', - 'style'=>'width: 250px;', - 'value'=>$cl_cf_1, - 'data'=>$custom_fields->options, - 'empty'=>array(''=>$i18n->getKey('dropdown.select')) - )); - } -} - -// Submit. -if ($request->isPost()) { - if ($request->getParameter('btn_start')) { - // Start button clicked. We need to create a new uncompleted record with only the start time. - $cl_finish = null; - - // Validate user input. - if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client) - $err->add($i18n->getKey('error.client')); - if ($custom_fields) { - if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $err->add($i18n->getKey('error.field'), $custom_fields->fields[0]['label']); - } - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $err->add($i18n->getKey('error.project')); - } - if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_task) $err->add($i18n->getKey('error.task')); - } - // Finished validating user input. - - // Prohibit creating entries in future. - if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($selected_date->after($browser_today)) - $err->add($i18n->getKey('error.future_date')); - } - - // Prohibit creating time entries in locked interval. - if ($user->isDateLocked($selected_date)) - $err->add($i18n->getKey('error.range_locked')); - - // Prohibit creating another uncompleted record. - if ($err->no() && $uncompleted) { - $err->add($i18n->getKey('error.uncompleted_exists')." ".$i18n->getKey('error.goto_uncompleted').""); - } - - // Prohibit creating an overlapping record. - if ($err->no()) { - if (ttTimeHelper::overlaps($user->getActiveUser(), $cl_date, $cl_start, $cl_finish)) - $err->add($i18n->getKey('error.overlap')); - } - - if ($err->no()) { - $id = ttTimeHelper::insert(array( - 'date' => $cl_date, - 'user_id' => $user->getActiveUser(), - 'client' => $cl_client, - 'project' => $cl_project, - 'task' => $cl_task, - 'start' => $cl_start, - 'finish' => $cl_finish, - 'duration' => $cl_duration, - 'note' => $cl_note, - 'billable' => $cl_billable)); - - // Insert a custom field if we have it. - $result = true; - if ($id && $custom_fields && $cl_cf_1) { - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) - $result = $custom_fields->insert($id, $custom_fields->fields[0]['id'], null, $cl_cf_1); - elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) - $result = $custom_fields->insert($id, $custom_fields->fields[0]['id'], $cl_cf_1, null); - } - - if ($id && $result) { - header('Location: timer.php'); - exit(); - } - $err->add($i18n->getKey('error.db')); - } - } - if ($request->getParameter('btn_stop')) { - // Stop button clicked. We need to finish an uncompleted record in progress. - $record = ttTimeHelper::getRecord($uncompleted['id'], $user->getActiveUser()); - - // Can we complete this record? - if (ttTimeHelper::isValidInterval($record['start'], $cl_finish) // finish time is greater than start time - && !ttTimeHelper::overlaps($user->getActiveUser(), $cl_date, $record['start'], $cl_finish)) { // no overlap - $res = ttTimeHelper::update(array( - 'id'=>$record['id'], - 'date'=>$cl_date, - 'user_id'=>$user->getActiveUser(), - 'client'=>$record['client_id'], - 'project'=>$record['project_id'], - 'task'=>$record['task_id'], - 'start'=>$record['start'], - 'finish'=>$cl_finish, - 'note'=>$record['comment'], - 'billable'=>$record['billable'])); - if ($res) { - header('Location: timer.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else { - // Cannot complete, redirect for manual edit. - header('Location: time_edit.php?id='.$record['id']); - exit(); - } - } -} // isPost - -$week_total = ttTimeHelper::getTimeForWeek($user->getActiveUser(), $cl_date); -$smarty->assign('week_total', $week_total); - -$smarty->assign('uncompleted', $uncompleted); - - - -$smarty->assign('time_records', ttTimeHelper::getRecords($user->getActiveUser(), $cl_date)); -$smarty->assign('day_total', ttTimeHelper::getTimeForDay($user->getActiveUser(), $cl_date)); -$smarty->assign('client_list', $client_list); -$smarty->assign('project_list', $project_list); -$smarty->assign('task_list', $task_list); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="fillDropdowns()"'); -$smarty->assign('timestring', $selected_date->toString($user->date_format)); -$smarty->assign('title', $i18n->getKey('title.time')); -$smarty->assign('content_page_name', 'mobile/timer.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/user_add.php b/mobile/user_add.php deleted file mode 100644 index 34c48222e..000000000 --- a/mobile/user_add.php +++ /dev/null @@ -1,174 +0,0 @@ -isPluginEnabled('cl')) - $clients = ttTeamHelper::getActiveClients($user->team_id); - -$assigned_projects = array(); -if ($request->isPost()) { - $cl_name = trim($request->getParameter('name')); - $cl_login = trim($request->getParameter('login')); - if (!$auth->isPasswordExternal()) { - $cl_password1 = $request->getParameter('pas1'); - $cl_password2 = $request->getParameter('pas2'); - } - $cl_email = trim($request->getParameter('email')); - $cl_role = $request->getParameter('role'); - if (!$cl_role) $cl_role = ROLE_USER; - $cl_client_id = $request->getParameter('client'); - $cl_rate = $request->getParameter('rate'); - $cl_projects = $request->getParameter('projects'); - if (is_array($cl_projects)) { - foreach ($cl_projects as $p) { - if (ttValidFloat($request->getParameter('rate_'.$p), true)) { - $project_with_rate = array(); - $project_with_rate['id'] = $p; - $project_with_rate['rate'] = $request->getParameter('rate_'.$p); - $assigned_projects[] = $project_with_rate; - } else - $err->add($i18n->getKey('error.field'), 'rate_'.$p); - } - } -} - -$form = new Form('userForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login)); -if (!$auth->isPasswordExternal()) { - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas1','aspassword'=>true,'value'=>$cl_password1)); - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas2','aspassword'=>true,'value'=>$cl_password2)); -} -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','value'=>$cl_email)); - -$roles[ROLE_USER] = $i18n->getKey('label.user'); -$roles[ROLE_COMANAGER] = $i18n->getKey('form.users.comanager'); -if ($user->isPluginEnabled('cl')) - $roles[ROLE_CLIENT] = $i18n->getKey('label.client'); -$form->addInput(array('type'=>'combobox','onchange'=>'handleClientControl()','name'=>'role','value'=>$cl_role,'data'=>$roles)); -if ($user->isPluginEnabled('cl')) - $form->addInput(array('type'=>'combobox','name'=>'client','value'=>$cl_client_id,'data'=>$clients,'datakeys'=>array('id', 'name'),'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - -$form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'rate','format'=>'.2','value'=>$cl_rate)); - -$projects = ttTeamHelper::getActiveProjects($user->team_id); - -// Define classes for the projects table. -class NameCellRenderer extends DefaultCellRenderer { - function render(&$table, $value, $row, $column, $selected = false) { - $this->setOptions(array('width'=>200,'valign'=>'top')); - $this->setValue(''); - return $this->toString(); - } -} -class RateCellRenderer extends DefaultCellRenderer { - function render(&$table, $value, $row, $column, $selected = false) { - global $assigned_projects; - $field = new FloatField('rate_'.$table->getValueAtName($row, 'id')); - $field->setFormName($table->getFormName()); - $field->setLocalization($GLOBALS['I18N']); - $field->setSize(5); - $field->setFormat('.2'); - foreach ($assigned_projects as $p) { - if ($p['id'] == $table->getValueAtName($row,'id')) $field->setValue($p['rate']); - } - $this->setValue($field->toStringControl()); - return $this->toString(); - } -} -// Create projects table. -$table = new Table('projects'); -$table->setIAScript('setDefaultRate'); -$table->setTableOptions(array('width'=>'100%','cellspacing'=>'1','cellpadding'=>'3','border'=>'0')); -$table->setRowOptions(array('valign'=>'top','class'=>'tableHeader')); -$table->setData($projects); -$table->setKeyField('id'); -$table->setValue($cl_projects); -$table->addColumn(new TableColumn('name', $i18n->getKey('label.project'), new NameCellRenderer())); -$table->addColumn(new TableColumn('p_rate', $i18n->getKey('form.users.rate'), new RateCellRenderer())); -$form->addInputElement($table); - -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.submit'))); - -if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name')); - if (!ttValidString($cl_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); - if (!$auth->isPasswordExternal()) { - if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); - if ($cl_password1 !== $cl_password2) - $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } - if (!ttValidEmail($cl_email, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - if (!ttValidFloat($cl_rate, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.users.default_rate')); - - if ($err->no()) { - if (!ttUserHelper::getUserByLogin($cl_login)) { - $fields = array( - 'name' => $cl_name, - 'login' => $cl_login, - 'password' => $cl_password1, - 'rate' => $cl_rate, - 'team_id' => $user->team_id, - 'role' => $cl_role, - 'client_id' => $cl_client_id, - 'projects' => $assigned_projects, - 'email' => $cl_email); - if (ttUserHelper::insert($fields)) { - header('Location: users.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.user_exists')); - } -} // isPost - -$smarty->assign('auth_external', $auth->isPasswordExternal()); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.userForm.name.focus();handleClientControl();"'); -$smarty->assign('title', $i18n->getKey('title.add_user')); -$smarty->assign('content_page_name', 'mobile/user_add.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/user_delete.php b/mobile/user_delete.php deleted file mode 100644 index aa25a5b35..000000000 --- a/mobile/user_delete.php +++ /dev/null @@ -1,99 +0,0 @@ -getParameter('id'); - -// We need user name and login to display. -$user_details = ttUserHelper::getUserDetails($user_id); - -// Security checks. -$ok_to_go = $user->canManageTeam(); // Are we authorized for user deletes? -if ($ok_to_go) $ok_to_go = $ok_to_go && $user_details; // Are we deleting a real user? -if ($ok_to_go) $ok_to_go = $ok_to_go && ($user->team_id == $user_details['team_id']); // User belongs to our team? -if ($ok_to_go && $user->isCoManager() && (ROLE_COMANAGER == $user_details['role'])) - $ok_to_go = ($user->id == $user_details['id']); // Comanager is not allowed to delete other comanagers. -if ($ok_to_go && $user->isCoManager() && (ROLE_MANAGER == $user_details['role'])) - $ok_to_go = false; // Comanager is not allowed to delete a manager. - -if (!$ok_to_go) - die ($i18n->getKey('error.sys')); -else - $smarty->assign('user_to_delete', $user_details['name']." (".$user_details['login'].")"); - -// Create confirmation form. -$form = new Form('userDeleteForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$user_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); - -if ($request->isPost()) { - if ($request->getParameter('btn_delete')) { - if (ttUserHelper::markDeleted($user_id)) { - // If we deleted the "on behalf" user reset its info in session. - if ($user_id == $user->behalf_id) { - unset($_SESSION['behalf_id']); - unset($_SESSION['behalf_name']); - } - // If we deleted our own account, do housekeeping and logout. - if ($user->id == $user_id) { - // Remove tt_login cookie that stores login name. - unset($_COOKIE['tt_login']); - setcookie('tt_login', NULL, -1); - - $auth->doLogout(); - header('Location: login.php'); - } else { - header('Location: users.php'); - } - exit(); - } else { - $err->add($i18n->getKey('error.db')); - } - } - if ($request->getParameter('btn_cancel')) { - header('Location: users.php'); - exit(); - } -} // isPost - -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.delete_user')); -$smarty->assign('content_page_name', 'mobile/user_delete.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mobile/user_edit.php b/mobile/user_edit.php deleted file mode 100644 index 4f3b9aaa0..000000000 --- a/mobile/user_edit.php +++ /dev/null @@ -1,245 +0,0 @@ -getParameter('id'); - -// Get user details. -$user_details = ttUserHelper::getUserDetails($user_id); - -// Security checks. -$ok_to_go = $user->canManageTeam(); // Are we authorized for user management? -if ($ok_to_go) $ok_to_go = $ok_to_go && $user_details; // Are we editing a real user? -if ($ok_to_go) $ok_to_go = $ok_to_go && ($user->team_id == $user_details['team_id']); // User belongs to our team? -if ($ok_to_go && $user->isCoManager() && (ROLE_COMANAGER == $user_details['role'])) - $ok_to_go = ($user->id == $user_details['id']); // Comanager is not allowed to edit other comanagers. -if ($ok_to_go && $user->isCoManager() && (ROLE_MANAGER == $user_details['role'])) - $ok_to_go = false; // Comanager is not allowed to edit a manager. -if (!$ok_to_go) { - die ($i18n->getKey('error.sys')); -} - -if ($user->isPluginEnabled('cl')) - $clients = ttTeamHelper::getActiveClients($user->team_id); - -$projects = ttTeamHelper::getActiveProjects($user->team_id); -$assigned_projects = array(); - -if ($request->isPost()) { - $cl_name = trim($request->getParameter('name')); - $cl_login = trim($request->getParameter('login')); - if (!$auth->isPasswordExternal()) { - $cl_password1 = $request->getParameter('pas1'); - $cl_password2 = $request->getParameter('pas2'); - } - $cl_email = trim($request->getParameter('email')); - $cl_role = $request->getParameter('role'); - $cl_client_id = $request->getParameter('client'); - $cl_status = $request->getParameter('status'); - $cl_rate = $request->getParameter('rate'); - $cl_projects = $request->getParameter('projects'); - if (is_array($cl_projects)) { - foreach ($cl_projects as $p) { - if (ttValidFloat($request->getParameter('rate_'.$p), true)) { - $project_with_rate = array(); - $project_with_rate['id'] = $p; - $project_with_rate['rate'] = $request->getParameter('rate_'.$p); - $assigned_projects[] = $project_with_rate; - } else - $err->add($i18n->getKey('error.field'), 'rate_'.$p); - } - } -} else { - $cl_name = $user_details['name']; - $cl_login = $user_details['login']; - $cl_email = $user_details['email']; - $cl_rate = str_replace('.', $user->decimal_mark, $user_details['rate']); - $cl_role = $user_details['role']; - $cl_client_id = $user_details['client_id']; - $cl_status = $user_details['status']; - $cl_projects = array(); - $assigned_projects = ttProjectHelper::getAssignedProjects($user_id); - foreach($assigned_projects as $p) { - $cl_projects[] = $p['id']; - } -} - -$form = new Form('userForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login)); -if (!$auth->isPasswordExternal()) { - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas1','aspassword'=>true,'value'=>$cl_password1)); - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas2','aspassword'=>true,'value'=>$cl_password2)); -} -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','value'=>$cl_email)); - -$roles[ROLE_USER] = $i18n->getKey('label.user'); -$roles[ROLE_COMANAGER] = $i18n->getKey('form.users.comanager'); -if ($user->isPluginEnabled('cl')) - $roles[ROLE_CLIENT] = $i18n->getKey('label.client'); -$form->addInput(array('type'=>'combobox','onchange'=>'handleClientControl()','name'=>'role','value'=>$cl_role,'data'=>$roles)); -if ($user->isPluginEnabled('cl')) - $form->addInput(array('type'=>'combobox','name'=>'client','value'=>$cl_client_id,'data'=>$clients,'datakeys'=>array('id', 'name'),'empty'=>array(''=>$i18n->getKey('dropdown.select')))); - -$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status, - 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive')))); -$form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'rate','format'=>'.2','value'=>$cl_rate)); - -// Define classes for the projects table. -class NameCellRenderer extends DefaultCellRenderer { - function render(&$table, $value, $row, $column, $selected = false) { - $this->setOptions(array('width'=>200,'valign'=>'top')); - $this->setValue(''); - return $this->toString(); - } -} -class RateCellRenderer extends DefaultCellRenderer { - function render(&$table, $value, $row, $column, $selected = false) { - global $assigned_projects; - $field = new FloatField('rate_'.$table->getValueAtName($row,'id')); - $field->setFormName($table->getFormName()); - $field->setLocalization($GLOBALS['I18N']); - $field->setSize(5); - $field->setFormat('.2'); - foreach ($assigned_projects as $p) { - if ($p['id'] == $table->getValueAtName($row,'id')) $field->setValue($p['rate']); - } - $this->setValue($field->toStringControl()); - return $this->toString(); - } -} -// Create projects table. -$table = new Table('projects'); -$table->setIAScript('setRate'); -$table->setTableOptions(array('width'=>'100%','cellspacing'=>'1','cellpadding'=>'3','border'=>'0')); -$table->setRowOptions(array('valign'=>'top','class'=>'tableHeader')); -$table->setData($projects); -$table->setKeyField('id'); -$table->setValue($cl_projects); -$table->addColumn(new TableColumn('name', $i18n->getKey('label.project'), new NameCellRenderer())); -$table->addColumn(new TableColumn('p_rate', $i18n->getKey('form.users.rate'), new RateCellRenderer())); -$form->addInputElement($table); - -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$user_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); - -if ($request->isPost()) { - if ($request->getParameter('btn_submit')) { - // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name')); - if (!ttValidString($cl_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); - if (!$auth->isPasswordExternal() && ($cl_password1 || $cl_password2)) { - if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); - if ($cl_password1 !== $cl_password2) - $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } - if (!ttValidEmail($cl_email, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - if (!ttValidFloat($cl_rate, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.users.default_rate')); - - if ($err->no()) { - $existing_user = ttUserHelper::getUserByLogin($cl_login); - if (!$existing_user || ($user_id == $existing_user['id'])) { - - $fields = array( - 'name' => $cl_name, - 'login' => $cl_login, - 'password' => $cl_password1, - 'email' => $cl_email, - 'status' => $cl_status, - 'rate' => $cl_rate, - 'projects' => $assigned_projects); - if (right_assign_roles & $user->rights) { - $fields['role'] = $cl_role; - $fields['client_id'] = $cl_client_id; - } - - if (ttUserHelper::update($user_id, $fields)) { - - // If our own login changed, set new one in cookie to remember it. - if (($user_id == $user->id) && ($user->login != $cl_login)) { - setcookie('tt_login', $cl_login, time() + COOKIE_EXPIRE, '/'); - } - - // In case the name of the "on behalf" user has changed - set it in session. - if (($user->behalf_id == $user_id) && ($user->behalf_name != $cl_name)) { - $_SESSION['behalf_name'] = $cl_name; - } - - // If we deactivated our own account, do housekeeping and logout. - if ($user->id == $user_id && !is_null($cl_status) && $cl_status == INACTIVE) { - // Remove tt_login cookie that stores login name. - unset($_COOKIE['tt_login']); - setcookie('tt_login', NULL, -1); - - $auth->doLogout(); - header('Location: login.php'); - exit(); - } - - header('Location: users.php'); - exit(); - - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.user_exists')); - } - } - - if ($request->getParameter('btn_delete')) { - header("Location: user_delete.php?id=$user_id"); - exit(); - } -} // isPost - -$rates = ttProjectHelper::getRates($user_id); -$smarty->assign('rates', $rates); - -$smarty->assign('auth_external', $auth->isPasswordExternal()); -$smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.userForm.name.focus();handleClientControl();"'); -$smarty->assign('user_id', $user_id); -$smarty->assign('title', $i18n->getKey('title.edit_user')); -$smarty->assign('content_page_name', 'mobile/user_edit.tpl'); -$smarty->display('mobile/index.tpl'); diff --git a/mysql.sql b/mysql.sql index d8294b2c4..f614d0ac8 100644 --- a/mysql.sql +++ b/mysql.sql @@ -3,96 +3,153 @@ # 2) Then, execute this script from command prompt with a command like this: # mysql -h host -u user -p -D db_name < mysql.sql -# create database timetracker character set = 'utf8'; +# create database timetracker character set = 'utf8mb4'; # use timetracker; # -# Structure for table tt_teams. A team is a group of users for whom we are tracking work time. -# This table stores settings common to all team members such as language, week start day, etc. -# -CREATE TABLE `tt_teams` ( - `id` int(11) NOT NULL auto_increment, # team id - `timestamp` timestamp NOT NULL, # modification timestamp - `name` varchar(80) default NULL, # team name - `address` varchar(255) default NULL, # team address, used in invoices - `currency` varchar(7) default NULL, # team currency symbol - `decimal_mark` char(1) NOT NULL default '.', # separator in decimals - `lang` varchar(10) NOT NULL default 'en', # language - `date_format` varchar(20) NOT NULL default '%Y-%m-%d', # date format - `time_format` varchar(20) NOT NULL default '%H:%M', # time format - `week_start` smallint(2) NOT NULL DEFAULT '0', # Week start day, 0 == Sunday. - `tracking_mode` smallint(2) NOT NULL DEFAULT '1', # tracking mode ("projects" or "projects and tasks") - `record_type` smallint(2) NOT NULL DEFAULT '0', # time record type ("start and finish", "duration", or both) - `uncompleted_indicators` smallint(2) NOT NULL DEFAULT '0', # whether to show indicators for users with uncompleted time entries - `plugins` varchar(255) default NULL, # a list of enabled plugins for team - `lock_spec` varchar(255) default NULL, # Cron specification for record locking, - # for example: "0 10 * * 1" for "weekly on Mon at 10:00". - `workday_hours` smallint(6) DEFAULT '8', # number of work hours in a regular day - `custom_logo` tinyint(4) default '0', # whether to use a custom logo or not - `status` tinyint(4) default '1', # team status +# Structure for table tt_groups. A group is a unit of users for whom we are tracking work time. +# This table stores settings common to all group members such as language, week start day, etc. +# +CREATE TABLE `tt_groups` ( + `id` int(11) NOT NULL auto_increment, # group id + `parent_id` int(11) default NULL, # parent group id + `org_id` int(11) default NULL, # organization id (id of top group) + `group_key` varchar(32) default NULL, # group key + `name` varchar(80) default NULL, # group name + `description` varchar(255) default NULL, # group description + `currency` varchar(7) default NULL, # currency symbol + `decimal_mark` char(1) NOT NULL default '.', # separator in decimals + `lang` varchar(10) NOT NULL default 'en', # language + `date_format` varchar(20) NOT NULL default '%Y-%m-%d', # date format + `time_format` varchar(20) NOT NULL default '%H:%M', # time format + `week_start` smallint(2) NOT NULL default 0, # Week start day, 0 == Sunday. + `tracking_mode` smallint(2) NOT NULL default 1, # tracking mode ("time", "projects" or "projects and tasks") + `project_required` smallint(2) NOT NULL default 0, # whether a project selection is required or optional + `record_type` smallint(2) NOT NULL default 0, # time record type ("start and finish", "duration", or both) + `bcc_email` varchar(100) default NULL, # bcc email to copy all reports to + `allow_ip` varchar(255) default NULL, # specification from where users are allowed access + `password_complexity` varchar(64) default NULL, # password example that defines required complexity + `plugins` varchar(255) default NULL, # a list of enabled plugins for group + `lock_spec` varchar(255) default NULL, # Cron specification for record locking, + # for example: "0 10 * * 1" for "weekly on Mon at 10:00". + `holidays` text default NULL, # holidays specification + `workday_minutes` smallint(4) default 480, # number of work minutes in a regular working day + `custom_logo` tinyint(4) default 0, # whether to use a custom logo or not + `config` text default NULL, # miscellaneous group configuration settings + `custom_css` text default NULL, # custom css for group + `custom_translation` text default NULL, # custom translation for group + `created` datetime default NULL, # creation timestamp + `created_ip` varchar(45) default NULL, # creator ip + `created_by` int(11) default NULL, # creator user_id + `modified` datetime default NULL, # modification timestamp + `modified_ip` varchar(45) default NULL, # modifier ip + `modified_by` int(11) default NULL, # modifier user_id + `entities_modified` datetime default NULL, # modification timestamp of group entities (clients, projects, etc.) + `status` tinyint(4) default 1, # group status PRIMARY KEY (`id`) ); +# +# Structure for table tt_roles. This table stores group roles. +# +CREATE TABLE `tt_roles` ( + `id` int(11) NOT NULL auto_increment, # Role id. Identifies roles for all groups on the server. + `group_id` int(11) NOT NULL, # Group id the role is defined for. + `org_id` int(11) default NULL, # Organization id. + `name` varchar(80) default NULL, # Role name - custom role name. In case we are editing a + # predefined role (USER, etc.), we can rename the role here. + `description` varchar(255) default NULL, # Role description. + `rank` int(11) default 0, # Role rank, an integer value between 0-512. Predefined role ranks: + # User - 4, Supervisor - 12, Client - 16, + # Co-manager - 68, Manager - 324, Top manager - 512. + # Rank is used to determine what "lesser roles" are in each group + # for situations such as "manage_users". + `rights` text default NULL, # Comma-separated list of rights assigned to a role. + # NULL here for predefined roles (4, 16, 68, 324 - manager) + # means a hard-coded set of default access rights. + `status` tinyint(4) default 1, # Role status. + PRIMARY KEY (`id`) +); + +# Create an index that guarantees unique active and inactive role ranks in each group. +create unique index role_idx on tt_roles(group_id, `rank`, status); + +# Insert site-wide roles - site administrator and top manager. +INSERT INTO `tt_roles` (`group_id`, `name`, `rank`, `rights`) VALUES (0, 'Site administrator', 1024, 'administer_site'); +INSERT INTO `tt_roles` (`group_id`, `name`, `rank`, `rights`) VALUES (0, 'Top manager', 512, 'track_own_time,track_own_expenses,view_own_reports,view_own_charts,view_own_projects,view_own_tasks,manage_own_settings,view_users,view_client_reports,view_client_invoices,track_time,track_expenses,view_reports,approve_reports,approve_timesheets,view_charts,view_own_clients,override_punch_mode,override_own_punch_mode,override_date_lock,override_own_date_lock,swap_roles,manage_own_account,manage_users,manage_projects,manage_tasks,manage_custom_fields,manage_clients,manage_invoices,override_allow_ip,manage_basic_settings,view_all_charts,view_all_reports,manage_features,manage_advanced_settings,manage_roles,export_data,approve_all_reports,approve_own_timesheets,manage_subgroups,view_client_unapproved,override_2fa,delete_group'); + + # # Structure for table tt_users. This table is used to store user properties. # CREATE TABLE `tt_users` ( - `id` int(11) NOT NULL auto_increment, # user id - `timestamp` timestamp NOT NULL, # modification timestamp - `login` varchar(50) COLLATE utf8_bin NOT NULL, # user login - `password` varchar(50) default NULL, # password hash - `name` varchar(100) default NULL, # user name - `team_id` int(11) NOT NULL, # team id - `role` int(11) default '4', # user role ("manager", "co-manager", "client", or "user") - `client_id` int(11) default NULL, # client id for "client" user role - `rate` float(6,2) NOT NULL default '0.00', # default hourly rate - `email` varchar(100) default NULL, # user email - `status` tinyint(4) default '1', # user status + `id` int(11) NOT NULL auto_increment, # user id + `login` varchar(80) COLLATE utf8mb4_bin NOT NULL,# user login + `password` varchar(50) default NULL, # password hash + `name` varchar(80) default NULL, # user name + `group_id` int(11) NOT NULL, # group id + `org_id` int(11) default NULL, # organization id + `role_id` int(11) default NULL, # role id + `client_id` int(11) default NULL, # client id for "client" user role + `rate` float(6,2) NOT NULL default '0.00', # default hourly rate + `quota_percent` float(6,2) NOT NULL default '100.00', # percent of time quota + `email` varchar(100) default NULL, # user email + `created` datetime default NULL, # creation timestamp + `created_ip` varchar(45) default NULL, # creator ip + `created_by` int(11) default NULL, # creator user_id (null for self) + `modified` datetime default NULL, # modification timestamp + `modified_ip` varchar(45) default NULL, # modifier ip + `modified_by` int(11) default NULL, # modifier user_id + `accessed` datetime default NULL, # last access timestamp + `accessed_ip` varchar(45) default NULL, # last access ip + `status` tinyint(4) default 1, # user status PRIMARY KEY (`id`) ); # Create an index that guarantees unique active and inactive logins. create unique index login_idx on tt_users(login, status); -# Create admin account with password 'secret'. Admin is a superuser, who can create teams. +# Create admin account with password 'secret'. Admin is a superuser who can create groups. DELETE from `tt_users` WHERE login = 'admin'; -INSERT INTO `tt_users` (`login`, `password`, `name`, `team_id`, `role`) VALUES ('admin', md5('secret'), 'Admin', '0', '1024'); +INSERT INTO `tt_users` (`login`, `password`, `name`, `group_id`, `role_id`) VALUES ('admin', md5('secret'), 'Admin', '0', (select id from tt_roles where `rank` = 1024)); # # Structure for table tt_projects. # CREATE TABLE `tt_projects` ( - `id` int(11) NOT NULL auto_increment, # project id - `team_id` int(11) NOT NULL, # team id - `name` varchar(80) COLLATE utf8_bin NOT NULL, # project name - `description` varchar(255) default NULL, # project description - `tasks` text default NULL, # comma-separated list of task ids associated with this project - `status` tinyint(4) default '1', # project status + `id` int(11) NOT NULL auto_increment, # project id + `group_id` int(11) NOT NULL, # group id + `org_id` int(11) default NULL, # organization id + `name` varchar(80) COLLATE utf8mb4_bin NOT NULL, # project name + `description` varchar(255) default NULL, # project description + `tasks` text default NULL, # comma-separated list of task ids associated with this project + `status` tinyint(4) default 1, # project status PRIMARY KEY (`id`) ); -# Create an index that guarantees unique active and inactive projects per team. -create unique index project_idx on tt_projects(team_id, name, status); +# Create an index that guarantees unique active and inactive projects per group. +create unique index project_idx on tt_projects(group_id, name, status); # # Structure for table tt_tasks. # CREATE TABLE `tt_tasks` ( - `id` int(11) NOT NULL auto_increment, # task id - `team_id` int(11) NOT NULL, # team id - `name` varchar(80) COLLATE utf8_bin NOT NULL, # task name - `description` varchar(255) default NULL, # task description - `status` tinyint(4) default '1', # task status + `id` int(11) NOT NULL auto_increment, # task id + `group_id` int(11) NOT NULL, # group id + `org_id` int(11) default NULL, # organization id + `name` varchar(80) COLLATE utf8mb4_bin NOT NULL, # task name + `description` varchar(255) default NULL, # task description + `status` tinyint(4) default 1, # task status PRIMARY KEY (`id`) ); -# Create an index that guarantees unique active and inactive tasks per team. -create unique index task_idx on tt_tasks(team_id, name, status); +# Create an index that guarantees unique active and inactive tasks per group. +create unique index task_idx on tt_tasks(group_id, name, status); # @@ -102,8 +159,10 @@ CREATE TABLE `tt_user_project_binds` ( `id` int(11) NOT NULL auto_increment, # bind id `user_id` int(11) NOT NULL, # user id `project_id` int(11) NOT NULL, # project id + `group_id` int(11) default NULL, # group id + `org_id` int(11) default NULL, # organization id `rate` float(6,2) default '0.00', # rate for this user when working on this project - `status` tinyint(4) default '1', # bind status + `status` tinyint(4) default 1, # bind status PRIMARY KEY (`id`) ); @@ -115,13 +174,16 @@ create unique index bind_idx on tt_user_project_binds(user_id, project_id); # Structure for table tt_project_task_binds. This table maps projects to assigned tasks. # CREATE TABLE `tt_project_task_binds` ( - `project_id` int(11) NOT NULL, # project id - `task_id` int(11) NOT NULL # task id + `project_id` int(11) NOT NULL, # project id + `task_id` int(11) NOT NULL, # task id + `group_id` int(11) default NULL, # group id + `org_id` int(11) default NULL # organization id ); # Indexes for tt_project_task_binds. create index project_idx on tt_project_task_binds(project_id); create index task_idx on tt_project_task_binds(task_id); +create unique index project_task_idx on tt_project_task_binds(project_id, task_id); # @@ -129,55 +191,68 @@ create index task_idx on tt_project_task_binds(task_id); # If you use custom fields, additional info for each record may exist in tt_custom_field_log. # CREATE TABLE `tt_log` ( - `id` bigint NOT NULL auto_increment, # time record id - `timestamp` timestamp NOT NULL, # modification timestamp - `user_id` int(11) NOT NULL, # user id - `date` date NOT NULL, # date the record is for - `start` time default NULL, # record start time (for example, 09:00) - `duration` time default NULL, # record duration (for example, 1 hour) - `client_id` int(11) default NULL, # client id - `project_id` int(11) default NULL, # project id - `task_id` int(11) default NULL, # task id - `invoice_id` int(11) default NULL, # invoice id - `comment` text, # user provided comment for time record - `billable` tinyint(4) default '0', # whether the record is billable or not - `status` tinyint(4) default '1', # time record status + `id` bigint NOT NULL auto_increment, # time record id + `user_id` int(11) NOT NULL, # user id + `group_id` int(11) default NULL, # group id + `org_id` int(11) default NULL, # organization id + `date` date NOT NULL, # date the record is for + `start` time default NULL, # record start time (for example, 09:00) + `duration` time default NULL, # record duration (for example, 1 hour) + `client_id` int(11) default NULL, # client id + `project_id` int(11) default NULL, # project id + `task_id` int(11) default NULL, # task id + `timesheet_id` int(11) default NULL, # timesheet id + `invoice_id` int(11) default NULL, # invoice id + `comment` text, # user provided comment for time record + `billable` tinyint(4) default 0, # whether the record is billable or not + `approved` tinyint(4) default 0, # whether the record is approved + `paid` tinyint(4) default 0, # whether the record is paid + `created` datetime default NULL, # creation timestamp + `created_ip` varchar(45) default NULL, # creator ip + `created_by` int(11) default NULL, # creator user_id + `modified` datetime default NULL, # modification timestamp + `modified_ip` varchar(45) default NULL, # modifier ip + `modified_by` int(11) default NULL, # modifier user_id + `status` tinyint(4) default 1, # time record status PRIMARY KEY (`id`) ); # Create indexes on tt_log for performance. create index date_idx on tt_log(date); create index user_idx on tt_log(user_id); +create index group_idx on tt_log(group_id); create index client_idx on tt_log(client_id); create index invoice_idx on tt_log(invoice_id); create index project_idx on tt_log(project_id); create index task_idx on tt_log(task_id); +create index timesheet_idx on tt_log(timesheet_id); # # Structure for table tt_invoices. Invoices are issued to clients for billable work. # CREATE TABLE `tt_invoices` ( - `id` int(11) NOT NULL auto_increment, # invoice id - `team_id` int(11) NOT NULL, # team id - `name` varchar(80) COLLATE utf8_bin NOT NULL, # invoice name - `date` date NOT NULL, # invoice date - `client_id` int(11) NOT NULL, # client id - `status` tinyint(4) default '1', # invoice status + `id` int(11) NOT NULL auto_increment, # invoice id + `group_id` int(11) NOT NULL, # group id + `org_id` int(11) default NULL, # organization id + `name` varchar(80) COLLATE utf8mb4_bin NOT NULL, # invoice name + `date` date NOT NULL, # invoice date + `client_id` int(11) NOT NULL, # client id + `status` tinyint(4) default 1, # invoice status PRIMARY KEY (`id`) ); -# Create an index that guarantees unique invoice names per team. -create unique index name_idx on tt_invoices(team_id, name, status); +# Create an index that guarantees unique invoice names per group. +create unique index name_idx on tt_invoices(group_id, name, status); # # Structure for table tt_tmp_refs. Used for reset password mechanism. # CREATE TABLE `tt_tmp_refs` ( - `timestamp` timestamp NOT NULL, # creation timestamp - `ref` char(32) NOT NULL default '', # unique reference for user, used in urls - `user_id` int(11) NOT NULL # user id + `created` datetime default NULL, # creation timestamp + `ref` char(32) NOT NULL default '', # unique reference for user, used in urls + `user_id` int(11) NOT NULL # user id ); @@ -188,28 +263,41 @@ CREATE TABLE `tt_fav_reports` ( `id` int(11) NOT NULL auto_increment, # favorite report id `name` varchar(200) NOT NULL, # favorite report name `user_id` int(11) NOT NULL, # user id favorite report belongs to + `group_id` int(11) default NULL, # group id + `org_id` int(11) default NULL, # organization id + `report_spec` text default NULL, # future replacement field for all report settings `client_id` int(11) default NULL, # client id (if selected) - `cf_1_option_id` int(11) default NULL, # custom field 1 option id (if selected) `project_id` int(11) default NULL, # project id (if selected) `task_id` int(11) default NULL, # task id (if selected) `billable` tinyint(4) default NULL, # whether to include billable, not billable, or all records + `approved` tinyint(4) default NULL, # whether to include approved, unapproved, or all records `invoice` tinyint(4) default NULL, # whether to include invoiced, not invoiced, or all records + `timesheet` tinyint(4) default NULL, # include records with a specific timesheet status, or all records + `paid_status` tinyint(4) default NULL, # whether to include paid, not paid, or all records + `note_containing` varchar(80) default NULL, # include only records with notes containing this text `users` text default NULL, # Comma-separated list of user ids. Nothing here means "all" users. `period` tinyint(4) default NULL, # selected period type for report `period_start` date default NULL, # period start `period_end` date default NULL, # period end - `show_client` tinyint(4) NOT NULL default '0', # whether to show client column - `show_invoice` tinyint(4) NOT NULL default '0', # whether to show invoice column - `show_project` tinyint(4) NOT NULL default '0', # whether to show project column - `show_start` tinyint(4) NOT NULL default '0', # whether to show start field - `show_duration` tinyint(4) NOT NULL default '0', # whether to show duration field - `show_cost` tinyint(4) NOT NULL default '0', # whether to show cost field - `show_task` tinyint(4) NOT NULL default '0', # whether to show task column - `show_end` tinyint(4) NOT NULL default '0', # whether to show end field - `show_note` tinyint(4) NOT NULL default '0', # whether to show note column - `show_custom_field_1` tinyint(4) NOT NULL default '0', # whether to show custom field 1 - `show_totals_only` tinyint(4) NOT NULL default '0', # whether to show totals only - `group_by` varchar(20) default NULL, # group by field + `show_client` tinyint(4) NOT NULL default 0, # whether to show client column + `show_invoice` tinyint(4) NOT NULL default 0, # whether to show invoice column + `show_paid` tinyint(4) NOT NULL default 0, # whether to show paid column + `show_ip` tinyint(4) NOT NULL default 0, # whether to show ip column + `show_project` tinyint(4) NOT NULL default 0, # whether to show project column + `show_timesheet` tinyint(4) NOT NULL default 0, # whether to show timesheet column + `show_start` tinyint(4) NOT NULL default 0, # whether to show start field + `show_duration` tinyint(4) NOT NULL default 0, # whether to show duration field + `show_cost` tinyint(4) NOT NULL default 0, # whether to show cost field + `show_task` tinyint(4) NOT NULL default 0, # whether to show task column + `show_end` tinyint(4) NOT NULL default 0, # whether to show end field + `show_note` tinyint(4) NOT NULL default 0, # whether to show note column + `show_approved` tinyint(4) NOT NULL default 0, # whether to show approved column + `show_work_units` tinyint(4) NOT NULL default 0, # whether to show work units + `show_totals_only` tinyint(4) NOT NULL default 0, # whether to show totals only + `group_by1` varchar(20) default NULL, # group by field 1 + `group_by2` varchar(20) default NULL, # group by field 2 + `group_by3` varchar(20) default NULL, # group by field 3 + `status` tinyint(4) default 1, # favorite report status PRIMARY KEY (`id`) ); @@ -219,13 +307,18 @@ CREATE TABLE `tt_fav_reports` ( # CREATE TABLE `tt_cron` ( `id` int(11) NOT NULL auto_increment, # entry id - `team_id` int(11) NOT NULL, # team id + `group_id` int(11) NOT NULL, # group id + `org_id` int(11) default NULL, # organization id `cron_spec` varchar(255) NOT NULL, # cron specification, "0 1 * * *" for "daily at 01:00" `last` int(11) default NULL, # UNIX timestamp of when job was last run `next` int(11) default NULL, # UNIX timestamp of when to run next job `report_id` int(11) default NULL, # report id from tt_fav_reports, a report to mail on schedule `email` varchar(100) default NULL, # email to send results to - `status` tinyint(4) default '1', # entry status + `cc` varchar(100) default NULL, # cc email to send results to + `subject` varchar(100) default NULL, # email subject + `comment` text, # user provided comment for notification + `report_condition` varchar(255) default NULL, # report condition, "count > 0" for sending not empty reports + `status` tinyint(4) default 1, # entry status PRIMARY KEY (`id`) ); @@ -234,31 +327,35 @@ CREATE TABLE `tt_cron` ( # Structure for table tt_clients. A client is an entity for whom work is performed and who may be invoiced. # CREATE TABLE `tt_clients` ( - `id` int(11) NOT NULL AUTO_INCREMENT, # client id - `team_id` int(11) NOT NULL, # team id - `name` varchar(80) COLLATE utf8_bin NOT NULL, # client name - `address` varchar(255) default NULL, # client address - `tax` float(6,2) default '0.00', # applicable tax for this client - `projects` text default NULL, # comma-separated list of project ids assigned to this client - `status` tinyint(4) default '1', # client status + `id` int(11) NOT NULL AUTO_INCREMENT, # client id + `group_id` int(11) NOT NULL, # group id + `org_id` int(11) default NULL, # organization id + `name` varchar(80) COLLATE utf8mb4_bin NOT NULL, # client name + `address` varchar(255) default NULL, # client address + `tax` float(6,2) default '0.00', # applicable tax for this client + `projects` text default NULL, # comma-separated list of project ids assigned to this client + `status` tinyint(4) default 1, # client status PRIMARY KEY (`id`) ); -# Create an index that guarantees unique active and inactive clients per team. -create unique index client_name_idx on tt_clients(team_id, name, status); +# Create an index that guarantees unique active and inactive clients per group. +create unique index client_name_idx on tt_clients(group_id, name, status); # # Structure for table tt_client_project_binds. This table maps clients to assigned projects. # CREATE TABLE `tt_client_project_binds` ( - `client_id` int(11) NOT NULL, # client id - `project_id` int(11) NOT NULL # project id + `client_id` int(11) NOT NULL, # client id + `project_id` int(11) NOT NULL, # project id + `group_id` int(11) default NULL, # group id + `org_id` int(11) default NULL # organization id ); # Indexes for tt_client_project_binds. create index client_idx on tt_client_project_binds(client_id); create index project_idx on tt_client_project_binds(project_id); +create unique index client_project_idx on tt_client_project_binds(client_id, project_id); # @@ -267,6 +364,8 @@ create index project_idx on tt_client_project_binds(project_id); # CREATE TABLE `tt_config` ( `user_id` int(11) NOT NULL, # user id + `group_id` int(11) default NULL, # group id + `org_id` int(11) default NULL, # organization id `param_name` varchar(32) NOT NULL, # parameter name `param_value` varchar(80) default NULL # parameter value ); @@ -282,11 +381,13 @@ create unique index param_idx on tt_config(user_id, param_name); # CREATE TABLE `tt_custom_fields` ( `id` int(11) NOT NULL auto_increment, # custom field id - `team_id` int(11) NOT NULL, # team id - `type` tinyint(4) NOT NULL default '0', # custom field type (text or dropdown) + `group_id` int(11) NOT NULL, # group id + `org_id` int(11) default NULL, # organization id + `entity_type` tinyint(4) default 1, # type of entity custom field is associated with (time, user, project, task, etc.) + `type` tinyint(4) NOT NULL default 0, # custom field type (text or dropdown) `label` varchar(32) NOT NULL default '', # custom field label - `required` tinyint(4) default '0', # whether this custom field is mandatory for time records - `status` tinyint(4) default '1', # custom field status + `required` tinyint(4) default 0, # whether this custom field is mandatory for time records + `status` tinyint(4) default 1, # custom field status PRIMARY KEY (`id`) ); @@ -296,8 +397,11 @@ CREATE TABLE `tt_custom_fields` ( # CREATE TABLE `tt_custom_field_options` ( `id` int(11) NOT NULL auto_increment, # option id + `group_id` int(11) default NULL, # group id + `org_id` int(11) default NULL, # organization id `field_id` int(11) NOT NULL, # custom field id `value` varchar(32) NOT NULL default '', # option value + `status` tinyint(4) default 1, # option status PRIMARY KEY (`id`) ); @@ -307,36 +411,81 @@ CREATE TABLE `tt_custom_field_options` ( # This table supplements tt_log and contains custom field values for records. # CREATE TABLE `tt_custom_field_log` ( - `id` bigint NOT NULL auto_increment, # cutom field log id + `id` bigint NOT NULL auto_increment, # custom field log id + `group_id` int(11) default NULL, # group id + `org_id` int(11) default NULL, # organization id `log_id` bigint NOT NULL, # id of a record in tt_log this record corresponds to `field_id` int(11) NOT NULL, # custom field id `option_id` int(11) default NULL, # Option id. Used for dropdown custom fields. `value` varchar(255) default NULL, # Text value. Used for text custom fields. - `status` tinyint(4) default '1', # custom field log entry status + `status` tinyint(4) default 1, # custom field log entry status + PRIMARY KEY (`id`) +); + +create index log_idx on tt_custom_field_log(log_id); + + +# +# Structure for table tt_entity_custom_fields. +# This table stores custom field values for entities such as users and projects +# except for "time" entity (and possibly "expense" in future). +# "time" custom fields are kept separately in tt_custom_field_log +# because tt_log (and tt_custom_field_log) can grow very large. +# +CREATE TABLE `tt_entity_custom_fields` ( + `id` int(10) unsigned NOT NULL auto_increment, # record id in this table + `group_id` int(10) unsigned NOT NULL, # group id + `org_id` int(10) unsigned NOT NULL, # organization id + `entity_type` tinyint(4) NOT NULL, # entity type + `entity_id` int(10) unsigned NOT NULL, # entity id this record corresponds to + `field_id` int(10) unsigned NOT NULL, # custom field id + `option_id` int(10) unsigned default NULL, # Option id. Used for dropdown custom fields. + `value` varchar(255) default NULL, # Text value. Used for text custom fields. + `created` datetime default NULL, # creation timestamp + `created_ip` varchar(45) default NULL, # creator ip + `created_by` int(10) unsigned default NULL, # creator user_id + `modified` datetime default NULL, # modification timestamp + `modified_ip` varchar(45) default NULL, # modifier ip + `modified_by` int(10) unsigned default NULL, # modifier user_id + `status` tinyint(4) default 1, # record status PRIMARY KEY (`id`) ); +# Create an index that guarantees unique custom fields per entity. +create unique index entity_idx on tt_entity_custom_fields(entity_type, entity_id, field_id); + # # Structure for table tt_expense_items. # This table lists expense items. # CREATE TABLE `tt_expense_items` ( - `id` bigint NOT NULL auto_increment, # expense item id - `date` date NOT NULL, # date the record is for - `user_id` int(11) NOT NULL, # user id the expense item is reported by - `client_id` int(11) default NULL, # client id - `project_id` int(11) default NULL, # project id - `name` text NOT NULL, # expense item name (what is an expense for) - `cost` decimal(10,2) default '0.00', # item cost (including taxes, etc.) - `invoice_id` int(11) default NULL, # invoice id - `status` tinyint(4) default '1', # item status + `id` bigint NOT NULL auto_increment, # expense item id + `date` date NOT NULL, # date the record is for + `user_id` int(11) NOT NULL, # user id the expense item is reported by + `group_id` int(11) default NULL, # group id + `org_id` int(11) default NULL, # organization id + `client_id` int(11) default NULL, # client id + `project_id` int(11) default NULL, # project id + `name` text NOT NULL, # expense item name (what is an expense for) + `cost` decimal(10,2) default '0.00', # item cost (including taxes, etc.) + `invoice_id` int(11) default NULL, # invoice id + `approved` tinyint(4) default 0, # whether the item is approved + `paid` tinyint(4) default 0, # whether the item is paid + `created` datetime default NULL, # creation timestamp + `created_ip` varchar(45) default NULL, # creator ip + `created_by` int(11) default NULL, # creator user_id + `modified` datetime default NULL, # modification timestamp + `modified_ip` varchar(45) default NULL, # modifier ip + `modified_by` int(11) default NULL, # modifier user_id + `status` tinyint(4) default 1, # item status PRIMARY KEY (`id`) ); # Create indexes on tt_expense_items for performance. create index date_idx on tt_expense_items(date); create index user_idx on tt_expense_items(user_id); +create index group_idx on tt_expense_items(group_id); create index client_idx on tt_expense_items(client_id); create index project_idx on tt_expense_items(project_id); create index invoice_idx on tt_expense_items(invoice_id); @@ -348,7 +497,8 @@ create index invoice_idx on tt_expense_items(invoice_id); # CREATE TABLE `tt_predefined_expenses` ( `id` int(11) NOT NULL auto_increment, # predefined expense id - `team_id` int(11) NOT NULL, # team id + `group_id` int(11) NOT NULL, # group id + `org_id` int(11) default NULL, # organization id `name` varchar(255) NOT NULL, # predefined expense name, such as mileage `cost` decimal(10,2) default '0.00', # cost for one unit PRIMARY KEY (`id`) @@ -357,15 +507,120 @@ CREATE TABLE `tt_predefined_expenses` ( # # Structure for table tt_monthly_quotas. -# This table keeps monthly work hour quotas for teams. +# This table keeps monthly work hour quotas for groups. # CREATE TABLE `tt_monthly_quotas` ( - `team_id` int(11) NOT NULL, # team id + `group_id` int(11) NOT NULL, # group id + `org_id` int(11) default NULL, # organization id `year` smallint(5) UNSIGNED NOT NULL, # quota year `month` tinyint(3) UNSIGNED NOT NULL, # quota month - `quota` smallint(5) UNSIGNED NOT NULL, # number of work hours in specified month and year - PRIMARY KEY (`team_id`,`year`,`month`) + `minutes` int(11) default NULL, # quota in minutes in specified month and year + PRIMARY KEY (`group_id`,`year`,`month`) +); + + +# +# Structure for table tt_timesheets. This table keeps timesheet related information. +# +CREATE TABLE `tt_timesheets` ( + `id` int(11) NOT NULL auto_increment, # timesheet id + `user_id` int(11) NOT NULL, # user id + `group_id` int(11) default NULL, # group id + `org_id` int(11) default NULL, # organization id + `client_id` int(11) default NULL, # client id + `project_id` int(11) default NULL, # project id + `name` varchar(80) COLLATE utf8mb4_bin NOT NULL, # timesheet name + `comment` text, # timesheet comment + `start_date` date NOT NULL, # timesheet start date + `end_date` date NOT NULL, # timesheet end date + `submit_status` tinyint(4) default NULL, # submit status + `approve_status` tinyint(4) default NULL, # approve status + `approve_comment` text, # approve comment + `created` datetime default NULL, # creation timestamp + `created_ip` varchar(45) default NULL, # creator ip + `created_by` int(11) default NULL, # creator user_id + `modified` datetime default NULL, # modification timestamp + `modified_ip` varchar(45) default NULL, # modifier ip + `modified_by` int(11) default NULL, # modifier user_id + `status` tinyint(4) default 1, # timesheet status + PRIMARY KEY (`id`) +); + + +# +# Structure for table tt_templates. +# This table keeps templates used in groups. +# +CREATE TABLE `tt_templates` ( + `id` int(11) NOT NULL auto_increment, # template id + `group_id` int(11) default NULL, # group id + `org_id` int(11) default NULL, # organization id + `name` varchar(80) COLLATE utf8mb4_bin NOT NULL, # template name + `description` varchar(255) default NULL, # template description + `content` text, # template content + `created` datetime default NULL, # creation timestamp + `created_ip` varchar(45) default NULL, # creator ip + `created_by` int(11) default NULL, # creator user_id + `modified` datetime default NULL, # modification timestamp + `modified_ip` varchar(45) default NULL, # modifier ip + `modified_by` int(11) default NULL, # modifier user_id + `status` tinyint(4) default 1, # template status + PRIMARY KEY (`id`) +); + + +# +# Structure for table tt_project_template_binds. This table maps projects to templates. +# +CREATE TABLE `tt_project_template_binds` ( + `project_id` int(10) unsigned NOT NULL, # project id + `template_id` int(10) unsigned NOT NULL, # template id + `group_id` int(10) unsigned NOT NULL, # group id + `org_id` int(10) unsigned NOT NULL # organization id +); + +# Indexes for tt_project_template_binds. +create index project_idx on tt_project_template_binds(project_id); +create index template_idx on tt_project_template_binds(template_id); +create unique index project_template_idx on tt_project_template_binds(project_id, template_id); + + +# +# Structure for table tt_files. +# This table keeps file attachment information. +# +CREATE TABLE `tt_files` ( + `id` int(10) unsigned NOT NULL auto_increment, # file id + `group_id` int(10) unsigned, # group id + `org_id` int(10) unsigned, # organization id + `remote_id` bigint(20) unsigned, # file id in storage facility + `file_key` varchar(32), # file key + `entity_type` varchar(32), # type of entity file is associated with (project, task, etc.) + `entity_id` int(10) unsigned, # entity id + `file_name` varchar(80) COLLATE utf8mb4_bin NOT NULL, # file name + `description` varchar(255) default NULL, # file description + `created` datetime default NULL, # creation timestamp + `created_ip` varchar(45) default NULL, # creator ip + `created_by` int(10) unsigned, # creator user_id + `modified` datetime default NULL, # modification timestamp + `modified_ip` varchar(45) default NULL, # modifier ip + `modified_by` int(10) unsigned, # modifier user_id + `status` tinyint(1) default 1, # file status + PRIMARY KEY (`id`) +); + + +# +# Structure for table tt_site_config. This table stores configuration data +# for Time Tracker site as a whole. +# For example, database version, code version, site language, etc. +# +CREATE TABLE `tt_site_config` ( + `param_name` varchar(32) NOT NULL, # parameter name + `param_value` text default NULL, # parameter value + `created` datetime default NULL, # creation timestamp + `modified` datetime default NULL, # modification timestamp + PRIMARY KEY (`param_name`) ); -ALTER TABLE `tt_monthly_quotas` - ADD CONSTRAINT `FK_TT_TEAM_CONSTRAING` FOREIGN KEY (`team_id`) REFERENCES `tt_teams` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; +INSERT INTO `tt_site_config` (`param_name`, `param_value`, `created`) VALUES ('version_db', '1.22.3', now()); # TODO: change when structure changes. diff --git a/notification_add.php b/notification_add.php index 05b6ce0c3..99602fe5d 100644 --- a/notification_add.php +++ b/notification_add.php @@ -1,30 +1,6 @@ isPluginEnabled('no')) { +// Access checks. +if (!ttAccessAllowed('manage_advanced_settings')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('no')) { + header('Location: feature_disabled.php'); + exit(); +} +if (!$user->exists()) { + header('Location: access_denied.php'); // No users in subgroup. + exit(); +} +$cl_fav_report_id = null; +if ($request->isPost() && $request->getParameter('fav_report')) { + $cl_fav_report_id = (int) $request->getParameter('fav_report'); + if (!ttFavReportHelper::get($cl_fav_report_id)) { + header('Location: access_denied.php'); // Invalid fav report id in post. + exit(); + } +} +// End of access checks. -$fav_reports = ttFavReportHelper::getReports($user->id); +$fav_reports = ttFavReportHelper::getReports(); +$cl_cron_spec = $cl_email = $cl_cc = $cl_subject = $cl_comment = $cl_report_condition = null; if ($request->isPost()) { - $cl_fav_report = trim($request->getParameter('fav_report')); $cl_cron_spec = trim($request->getParameter('cron_spec')); $cl_email = trim($request->getParameter('email')); + $cl_cc = trim($request->getParameter('cc')); + $cl_subject = trim($request->getParameter('subject')); + $cl_comment = trim($request->getParameter('comment')); + $cl_report_condition = trim($request->getParameter('report_condition')); } else { $cl_cron_spec = '0 4 * * 1'; // Default schedule - weekly on Mondays at 04:00 (server time). } @@ -52,41 +49,51 @@ $form = new Form('notificationForm'); $form->addInput(array('type'=>'combobox', 'name'=>'fav_report', - 'style'=>'width: 250px;', - 'value'=>$cl_fav_report, + 'value'=>$cl_fav_report_id, 'data'=>$fav_reports, 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')) + 'empty'=>array(''=>$i18n->get('dropdown.select')) )); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'cron_spec','style'=>'width: 250px;','value'=>$cl_cron_spec)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','style'=>'width: 250px;','value'=>$cl_email)); -$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add'))); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'cron_spec','value'=>$cl_cron_spec)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','value'=>$cl_email)); +$form->addInput(array('type'=>'text','name'=>'cc','value'=>$cl_cc)); +$form->addInput(array('type'=>'text','name'=>'subject','value'=>$cl_subject)); +$form->addInput(array('type'=>'textarea','name'=>'comment','value'=>$cl_comment)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'report_condition','value'=>$cl_report_condition)); +$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); if ($request->isPost()) { // Validate user input. - if (!$cl_fav_report) $err->add($i18n->getKey('error.report')); - if (!ttValidCronSpec($cl_cron_spec)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.cron_schedule')); - if (!ttValidEmail($cl_email)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); + if (!$cl_fav_report_id) $err->add($i18n->get('error.report')); + if (!ttValidCronSpec($cl_cron_spec)) $err->add($i18n->get('error.field'), $i18n->get('label.schedule')); + if (!ttValidEmailList($cl_email)) $err->add($i18n->get('error.field'), $i18n->get('label.email')); + if (!ttValidEmailList($cl_cc, true)) $err->add($i18n->get('error.field'), $i18n->get('label.cc')); + if (!ttValidString($cl_subject, true)) $err->add($i18n->get('error.field'), $i18n->get('label.subject')); + if (!ttValidString($cl_comment, true)) $err->add($i18n->get('error.field'), $i18n->get('label.comment')); + if (!ttValidCondition($cl_report_condition)) $err->add($i18n->get('error.field'), $i18n->get('label.condition')); if ($err->no()) { // Calculate next execution time. - $next = tdCron::getNextOccurrence($cl_cron_spec, mktime()); + $next = tdCron::getNextOccurrence($cl_cron_spec, time()); if (ttNotificationHelper::insert(array( - 'team_id' => $user->team_id, 'cron_spec' => $cl_cron_spec, 'next' => $next, - 'report_id' => $cl_fav_report, + 'report_id' => $cl_fav_report_id, 'email' => $cl_email, + 'cc' => $cl_cc, + 'subject' => $cl_subject, + 'comment' => $cl_comment, + 'report_condition' => $cl_report_condition, 'status' => ACTIVE))) { header('Location: notifications.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.add_notification')); +$smarty->assign('title', $i18n->get('title.add_notification')); $smarty->assign('content_page_name', 'notification_add.tpl'); $smarty->display('index.tpl'); diff --git a/notification_delete.php b/notification_delete.php index 834befbe3..93820427c 100644 --- a/notification_delete.php +++ b/notification_delete.php @@ -1,60 +1,46 @@ isPluginEnabled('no')) { +// Access checks. +if (!ttAccessAllowed('manage_advanced_settings')) { header('Location: access_denied.php'); exit(); } - +if (!$user->isPluginEnabled('no')) { + header('Location: feature_disabled.php'); + exit(); +} +if (!$user->exists()) { + header('Location: access_denied.php'); // No users in subgroup. + exit(); +} $cl_notification_id = (int)$request->getParameter('id'); $notification = ttNotificationHelper::get($cl_notification_id); +if (!$notification) { + header('Location: access_denied.php'); // Wrong notification id. + exit(); +} +// End of access checks. + $notification_to_delete = $notification['name']; $form = new Form('notificationDeleteForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_notification_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); if ($request->isPost()) { if ($request->getParameter('btn_delete')) { - if(ttNotificationHelper::get($cl_notification_id)) { - if (ttNotificationHelper::delete($cl_notification_id)) { - header('Location: notifications.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); + if (ttNotificationHelper::delete($cl_notification_id)) { + header('Location: notifications.php'); + exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } elseif ($request->getParameter('btn_cancel')) { header('Location: notifications.php'); exit(); @@ -64,6 +50,6 @@ $smarty->assign('notification_to_delete', $notification_to_delete); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.notificationDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->getKey('title.delete_notification')); +$smarty->assign('title', $i18n->get('title.delete_notification')); $smarty->assign('content_page_name', 'notification_delete.tpl'); $smarty->display('index.tpl'); diff --git a/notification_edit.php b/notification_edit.php index cd3f41bd1..164127e93 100644 --- a/notification_edit.php +++ b/notification_edit.php @@ -1,30 +1,6 @@ isPluginEnabled('no')) { +// Access checks. +if (!ttAccessAllowed('manage_advanced_settings')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('no')) { + header('Location: feature_disabled.php'); + exit(); +} +if (!$user->exists()) { + header('Location: access_denied.php'); // No users in subgroup. + exit(); +} +$notification_id = (int)$request->getParameter('id'); +$notification = ttNotificationHelper::get($notification_id); +if (!$notification) { + header('Location: access_denied.php'); // Wrong notification id. + exit(); +} +if ($request->isPost()) { + $cl_fav_report_id = (int) $request->getParameter('fav_report'); + if ($cl_fav_report_id && !ttFavReportHelper::get($cl_fav_report_id)) { + header('Location: access_denied.php'); // Invalid fav report id in post. + exit(); + } +} +// End of access checks. -$notification_id = (int) $request->getParameter('id'); -$fav_reports = ttFavReportHelper::getReports($user->id); +$fav_reports = ttFavReportHelper::getReports(); if ($request->isPost()) { - $cl_fav_report = trim($request->getParameter('fav_report')); $cl_cron_spec = trim($request->getParameter('cron_spec')); $cl_email = trim($request->getParameter('email')); + $cl_cc = trim($request->getParameter('cc')); + $cl_subject = trim($request->getParameter('subject')); + $cl_comment = trim($request->getParameter('comment')); + $cl_report_condition = trim($request->getParameter('report_condition')); } else { $notification = ttNotificationHelper::get($notification_id); - $cl_fav_report = $notification['report_id']; + $cl_fav_report_id = $notification['report_id']; $cl_cron_spec = $notification['cron_spec']; $cl_email = $notification['email']; + $cl_cc = $notification['cc']; + $cl_subject = $notification['subject']; + $cl_comment = $notification['comment']; + $cl_report_condition = $notification['report_condition']; } $form = new Form('notificationForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$notification_id)); $form->addInput(array('type'=>'combobox', 'name'=>'fav_report', - 'style'=>'width: 250px;', - 'value'=>$cl_fav_report, + 'value'=>$cl_fav_report_id, 'data'=>$fav_reports, 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'cron_spec','style'=>'width: 250px;','value'=>$cl_cron_spec)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','style'=>'width: 250px;','value'=>$cl_email)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.save'))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'cron_spec','value'=>$cl_cron_spec)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','value'=>$cl_email)); +$form->addInput(array('type'=>'text','name'=>'cc','value'=>$cl_cc)); +$form->addInput(array('type'=>'text','name'=>'subject','value'=>$cl_subject)); +$form->addInput(array('type'=>'textarea','name'=>'comment','value'=>$cl_comment)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'report_condition','value'=>$cl_report_condition)); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.save'))); if ($request->isPost()) { // Validate user input. - if (!$cl_fav_report) $err->add($i18n->getKey('error.report')); - if (!ttValidCronSpec($cl_cron_spec)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.cron_schedule')); - if (!ttValidEmail($cl_email)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); + if (!$cl_fav_report_id) $err->add($i18n->get('error.report')); + if (!ttValidCronSpec($cl_cron_spec)) $err->add($i18n->get('error.field'), $i18n->get('label.schedule')); + if (!ttValidEmailList($cl_email)) $err->add($i18n->get('error.field'), $i18n->get('form.email')); + if (!ttValidEmailList($cl_cc, true)) $err->add($i18n->get('error.field'), $i18n->get('label.cc')); + if (!ttValidString($cl_subject, true)) $err->add($i18n->get('error.field'), $i18n->get('label.subject')); + if (!ttValidString($cl_comment, true)) $err->add($i18n->get('error.field'), $i18n->get('label.comment')); + if (!ttValidCondition($cl_report_condition)) $err->add($i18n->get('error.field'), $i18n->get('label.condition')); if ($err->no()) { // Calculate next execution time. - $next = tdCron::getNextOccurrence($cl_cron_spec, mktime()); - + $next = tdCron::getNextOccurrence($cl_cron_spec, time()); if (ttNotificationHelper::update(array( 'id' => $notification_id, - 'team_id' => $user->team_id, 'cron_spec' => $cl_cron_spec, 'next' => $next, - 'report_id' => $cl_fav_report, + 'report_id' => $cl_fav_report_id, 'email' => $cl_email, + 'cc' => $cl_cc, + 'subject' => $cl_subject, + 'comment' => $cl_comment, + 'report_condition' => $cl_report_condition, 'status' => ACTIVE))) { header('Location: notifications.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.add_notification')); +$smarty->assign('title', $i18n->get('title.edit_notification')); $smarty->assign('content_page_name', 'notification_edit.tpl'); $smarty->display('index.tpl'); diff --git a/notifications.php b/notifications.php index 68cdfff8b..5945320ef 100644 --- a/notifications.php +++ b/notifications.php @@ -1,40 +1,28 @@ isPluginEnabled('no')) { +// Access checks. +if (!ttAccessAllowed('manage_advanced_settings')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('no')) { + header('Location: feature_disabled.php'); + exit(); +} +if (!$user->exists()) { + header('Location: access_denied.php'); // No users in subgroup. + exit(); +} +// End of access checks. + +// TODO: extend and re-design notifications. +// Currently they only work with fav reports, which are bound to users. $form = new Form('notificationsForm'); @@ -45,12 +33,12 @@ exit(); } } else { - $form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add'))); - $notifications = ttTeamHelper::getNotifications($user->team_id); + $form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); + $notifications = ttGroupHelper::getNotifications(); } $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('notifications', $notifications); -$smarty->assign('title', $i18n->getKey('title.notifications')); +$smarty->assign('title', $i18n->get('title.notifications')); $smarty->assign('content_page_name', 'notifications.tpl'); $smarty->display('index.tpl'); diff --git a/password_change.php b/password_change.php index b6fd4dd9a..d395b6396 100644 --- a/password_change.php +++ b/password_change.php @@ -1,30 +1,6 @@ doLogout(); +// Access checks. $cl_ref = $request->getParameter('ref'); if (!$cl_ref || $auth->isPasswordExternal()) { header('Location: login.php'); exit(); } - -// Get user ID. $user_id = ttUserHelper::getUserIdByTmpRef($cl_ref); -if ($user_id) { - $user = new ttUser(null, $user_id); // Note: reusing $user from initialize.php. - // In case user language is different - reload $i18n. - if ($i18n->lang != $user->lang) { - $i18n->load($user->lang); - $smarty->assign('i18n', $i18n->keys); - } - if ($user->custom_logo) { - $smarty->assign('custom_logo', 'images/'.$user->team_id.'.png'); - $smarty->assign('mobile_custom_logo', '../images/'.$user->team_id.'.png'); - } - $smarty->assign('user', $user); +if (!$user_id) { + header('Location: access_denied.php'); // No user found by provided reference. + exit(); +} +// End of access checks. + +$user = new ttUser(null, $user_id); // Note: reusing $user from initialize.php. +// In case user language is different - reload $i18n. +if ($i18n->lang != $user->lang) { + $i18n->load($user->lang); + $smarty->assign('i18n', $i18n->keys); +} +if ($user->custom_logo) { + $smarty->assign('custom_logo', 'img/'.$user->group_id.'.png'); + $smarty->assign('mobile_custom_logo', '../img/'.$user->group_id.'.png'); } +$smarty->assign('user', $user); $cl_password1 = $request->getParameter('password1'); $cl_password2 = $request->getParameter('password2'); $form = new Form('newPasswordForm'); -$form->addInput(array('type'=>'text','maxlength'=>'120','name'=>'password1','aspassword'=>true,'value'=>$cl_password1)); -$form->addInput(array('type'=>'text','maxlength'=>'120','name'=>'password2','aspassword'=>true,'value'=>$cl_password2)); +$form->addInput(array('type'=>'password','maxlength'=>'120','name'=>'password1','value'=>$cl_password1)); +$form->addInput(array('type'=>'password','maxlength'=>'120','name'=>'password2','value'=>$cl_password2)); $form->addInput(array('type'=>'hidden','name'=>'ref','value'=>$cl_ref)); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); + if (!ttValidString($cl_password1)) $err->add($i18n->get('error.field'), $i18n->get('label.password')); + if (!ttValidString($cl_password2)) $err->add($i18n->get('error.field'), $i18n->get('label.confirm_password')); if ($cl_password1 !== $cl_password2) - $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); + $err->add($i18n->get('error.not_equal'), $i18n->get('label.password'), $i18n->get('label.confirm_password')); + // Check password complexity. + if (!ttCheckPasswordComplexity($cl_password1)) + $err->add($i18n->get('error.weak_password')); if ($err->no()) { // Use the "limit" plugin if we have one. Ignore include errors. @@ -80,10 +62,10 @@ ttUserHelper::setPassword($user_id, $cl_password1); if ($auth->doLogin($user->login, $cl_password1)) { - setcookie('tt_login', $user->login, time() + COOKIE_EXPIRE, '/'); + setcookie(LOGIN_COOKIE_NAME, $user->login, time() + COOKIE_EXPIRE, '/'); // Redirect, depending on user role. - if ($user->isAdmin()) { - header('Location: admin_teams.php'); + if ($user->can('administer_site')) { + header('Location: admin_groups.php'); } elseif ($user->isClient()) { header('Location: reports.php'); } else { @@ -91,12 +73,12 @@ } exit(); } else { - $err->add($i18n->getKey('error.auth')); + $err->add($i18n->get('error.auth')); } } } // isPost $smarty->assign('forms', array($form->getName() => $form->toArray())); -$smarty->assign('title', $i18n->getKey('title.change_password')); +$smarty->assign('title', $i18n->get('title.change_password')); $smarty->assign('content_page_name', 'password_change.tpl'); $smarty->display('index.tpl'); diff --git a/password_reset.php b/password_reset.php index f1780bc20..9645139e4 100644 --- a/password_reset.php +++ b/password_reset.php @@ -1,30 +1,6 @@ getParameter('login'); + $form = new Form('resetPasswordForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','style'=>'width: 300px;')); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.reset_password'))); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login)); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.reset_password'))); if ($request->isPost()) { - $cl_login = $request->getParameter('login'); - // Validate user input. - if (!ttValidString($cl_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); + if (!ttValidString($cl_login)) $err->add($i18n->get('error.field'), $i18n->get('label.login')); if ($err->no()) { if (!ttUserHelper::getUserByLogin($cl_login)) { @@ -55,17 +31,25 @@ if ($login) $cl_login = $login; else - $err->add($i18n->getKey('error.no_login')); + $err->add($i18n->get('error.no_login')); } else - $err->add($i18n->getKey('error.no_login')); + $err->add($i18n->get('error.no_login')); } } if ($err->no()) { $user = new ttUser($cl_login); // Note: reusing $user from initialize.php here. + // Protection against flooding user mailbox with too many password reset emails. + if (ttUserHelper::recentRefExists($user->id)) $err->add($i18n->get('error.access_denied')); + } + + if ($err->no()) { // Prepare and save a temporary reference for user. - $temp_ref = md5(uniqid()); + $cryptographically_strong = true; + $random_bytes = openssl_random_pseudo_bytes(16, $cryptographically_strong); + if ($random_bytes === false) die ("openssl_random_pseudo_bytes function call failed..."); + $temp_ref = bin2hex($random_bytes); ttUserHelper::saveTmpRef($temp_ref, $user->id); $user_i18n = null; @@ -83,15 +67,16 @@ if (ttValidEmail($cl_login)) $receiver = $cl_login; else - $err->add($i18n->getKey('error.no_email')); + $err->add($i18n->get('error.no_email')); } if ($receiver) { import('mail.Mailer'); - $sender = new Mailer(); - $sender->setCharSet(CHARSET); - $sender->setSender(SENDER); - $sender->setReceiver("$receiver"); + $mailer = new Mailer(); + $mailer->setCharSet(CHARSET); + $mailer->setSender(SENDER); + $mailer->setReceiver("$receiver"); + $secure_connection = false; if ((!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] !== 'off')) || ($_SERVER['SERVER_PORT'] == 443)) $secure_connection = true; if($secure_connection) @@ -99,21 +84,27 @@ else $http = 'http'; - $cl_subject = $user_i18n->getKey('form.reset_password.email_subject'); - if (APP_NAME) - $pass_edit_url = $http.'://'.$_SERVER['HTTP_HOST'].'/'.APP_NAME.'/password_change.php?ref='.$temp_ref; - else - $pass_edit_url = $http.'://'.$_SERVER['HTTP_HOST'].'/password_change.php?ref='.$temp_ref; + $cl_subject = $user_i18n->get('form.reset_password.email_subject'); + + $dir_name = $app_root = ''; + if (defined('DIR_NAME')) + $dir_name = trim(constant('DIR_NAME'), '/'); + if (!empty($dir_name)) + $app_root = '/'.$dir_name; - $sender->setMailMode(MAIL_MODE); - $res = $sender->send($cl_subject, sprintf($user_i18n->getKey('form.reset_password.email_body'), $pass_edit_url)); - $smarty->assign('result_message', $res ? $i18n->getKey('form.reset_password.message') : $i18n->getKey('error.mail_send')); + $pass_edit_url = $http.'://'.$_SERVER['HTTP_HOST'].$app_root.'/password_change.php?ref='.$temp_ref; + + $mailer->setMailMode(MAIL_MODE); + if ($mailer->send($cl_subject, sprintf($user_i18n->get('form.reset_password.email_body'), $_SERVER['REMOTE_ADDR'], $pass_edit_url))) + $msg->add($i18n->get('form.reset_password.message')); + else + $err->add($i18n->get('error.mail_send')); } } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.resetPasswordForm.login.focus()"'); -$smarty->assign('title', $i18n->getKey('title.reset_password')); +$smarty->assign('title', $i18n->get('title.reset_password')); $smarty->assign('content_page_name', 'password_reset.tpl'); $smarty->display('index.tpl'); diff --git a/phpinfo.php b/phpinfo.php deleted file mode 100644 index 61ace196d..000000000 --- a/phpinfo.php +++ /dev/null @@ -1,2 +0,0 @@ -isPost()) { + // Plugins that user wants to save for the current group. + $cl_charts = (bool)$request->getParameter('charts'); + $cl_puncher = (bool)$request->getParameter('puncher'); + $cl_clients = (bool)$request->getParameter('clients'); + $cl_client_required = (bool)$request->getParameter('client_required'); + $cl_invoices = (bool)$request->getParameter('invoices'); + $cl_paid_status = (bool)$request->getParameter('paid_status'); + $cl_custom_fields = (bool)$request->getParameter('custom_fields'); + $cl_expenses = (bool)$request->getParameter('expenses'); + $cl_tax_expenses = (bool)$request->getParameter('tax_expenses'); + $cl_notifications = (bool)$request->getParameter('notifications'); + $cl_locking = (bool)$request->getParameter('locking'); + $cl_quotas = (bool)$request->getParameter('quotas'); + $cl_week_view = (bool)$request->getParameter('week_view'); + $cl_work_units = (bool)$request->getParameter('work_units'); + $cl_approval = (bool)$request->getParameter('approval'); + $cl_timesheets = (bool)$request->getParameter('timesheets'); + $cl_templates = (bool)$request->getParameter('templates'); + $cl_attachments = (bool)$request->getParameter('attachments'); +} else { + // Note: we get here in get, and also in post when group changes. + // Which plugins do we have enabled in currently selected group? + $plugins = explode(',', $user->getPlugins() ?? ''); + $cl_charts = in_array('ch', $plugins); + $cl_puncher = in_array('pu', $plugins); + $cl_clients = in_array('cl', $plugins); + $cl_client_required = $user->isOptionEnabled('client_required'); + $cl_invoices = in_array('iv', $plugins); + $cl_paid_status = in_array('ps', $plugins); + $cl_custom_fields = in_array('cf', $plugins); + $cl_expenses = in_array('ex', $plugins); + $cl_tax_expenses = in_array('et', $plugins); + $cl_notifications = in_array('no', $plugins); + $cl_locking = in_array('lk', $plugins); + $cl_quotas = in_array('mq', $plugins); + $cl_week_view = in_array('wv', $plugins); + $cl_work_units = in_array('wu', $plugins); + $cl_approval = in_array('ap', $plugins); + $cl_timesheets = in_array('ts', $plugins); + $cl_templates = in_array('tp', $plugins); + $cl_attachments = in_array('at', $plugins); +} + +$form = new Form('pluginsForm'); + +// Plugin checkboxes. +$form->addInput(array('type'=>'checkbox','name'=>'charts','value'=>$cl_charts)); +$form->addInput(array('type'=>'checkbox','name'=>'clients','value'=>$cl_clients,'onchange'=>'handlePluginCheckboxes()')); +$form->addInput(array('type'=>'checkbox','name'=>'client_required','value'=>$cl_client_required)); +$form->addInput(array('type'=>'checkbox','name'=>'invoices','value'=>$cl_invoices)); +$form->addInput(array('type'=>'checkbox','name'=>'paid_status','value'=>$cl_paid_status)); +$form->addInput(array('type'=>'checkbox','name'=>'custom_fields','value'=>$cl_custom_fields,'onchange'=>'handlePluginCheckboxes()')); +$form->addInput(array('type'=>'checkbox','name'=>'expenses','value'=>$cl_expenses,'onchange'=>'handlePluginCheckboxes()')); +$form->addInput(array('type'=>'checkbox','name'=>'tax_expenses','value'=>$cl_tax_expenses)); +$form->addInput(array('type'=>'checkbox','name'=>'notifications','value'=>$cl_notifications,'onchange'=>'handlePluginCheckboxes()')); +$form->addInput(array('type'=>'checkbox','name'=>'locking','value'=>$cl_locking,'onchange'=>'handlePluginCheckboxes()')); +$form->addInput(array('type'=>'checkbox','name'=>'quotas','value'=>$cl_quotas,'onchange'=>'handlePluginCheckboxes()')); +$form->addInput(array('type'=>'checkbox','name'=>'puncher','value'=>$cl_puncher,'onchange'=>'handlePluginCheckboxes()')); +$form->addInput(array('type'=>'checkbox','name'=>'week_view','value'=>$cl_week_view,'onchange'=>'handlePluginCheckboxes()')); +$form->addInput(array('type'=>'checkbox','name'=>'work_units','value'=>$cl_work_units,'onchange'=>'handlePluginCheckboxes()')); +$form->addInput(array('type'=>'checkbox','name'=>'approval','value'=>$cl_approval)); +$form->addInput(array('type'=>'checkbox','name'=>'timesheets','value'=>$cl_timesheets)); +$form->addInput(array('type'=>'checkbox','name'=>'templates','value'=>$cl_templates,'onchange'=>'handlePluginCheckboxes()')); +$form->addInput(array('type'=>'checkbox','name'=>'attachments','value'=>$cl_attachments,'onchange'=>'handlePluginCheckboxes()')); + +// Submit button. +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); + +if ($request->isPost()) { + // Note: we get here when the Save button is clicked. + // We update plugin list for the current group. + + // Prepare plugins string. + $plugins = ''; + if ($cl_charts) + $plugins .= ',ch'; + if ($cl_puncher) + $plugins .= ',pu'; + if ($cl_clients) + $plugins .= ',cl'; + if ($cl_invoices) + $plugins .= ',iv'; + if ($cl_paid_status) + $plugins .= ',ps'; + if ($cl_custom_fields) + $plugins .= ',cf'; + if ($cl_expenses) + $plugins .= ',ex'; + if ($cl_tax_expenses) + $plugins .= ',et'; + if ($cl_notifications) + $plugins .= ',no'; + if ($cl_locking) + $plugins .= ',lk'; + if ($cl_quotas) + $plugins .= ',mq'; + if ($cl_week_view) + $plugins .= ',wv'; + if ($cl_work_units) + $plugins .= ',wu'; + if ($cl_approval) + $plugins .= ',ap'; + if ($cl_timesheets) + $plugins .= ',ts'; + if ($cl_templates) + $plugins .= ',tp'; + if ($cl_attachments) + $plugins .= ',at'; + $plugins = trim($plugins, ','); + + // Prepare a new config string. + $user->setOption('client_required', $cl_client_required); + $user->setOption('tax_expenses', $cl_tax_expenses); + $config = $user->getConfig(); + + if ($user->updateGroup(array( + 'plugins' => $plugins, + 'config' => $config))) { + header('Location: plugins.php'); + exit(); + } else + $err->add($i18n->get('error.db')); +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="handlePluginCheckboxes();"'); +$smarty->assign('user_exists', $user->exists()); +$smarty->assign('title', $i18n->get('title.plugins')); +$smarty->assign('content_page_name', 'plugins.tpl'); +$smarty->display('index.tpl'); diff --git a/plugins/CustomFields.class.php b/plugins/CustomFields.class.php index f9592f1c8..b561af804 100644 --- a/plugins/CustomFields.class.php +++ b/plugins/CustomFields.class.php @@ -1,327 +1,583 @@ getGroup(); + $org_id = $user->org_id; + + // Get fields. + $sql = "select id, entity_type, type, label, required from tt_custom_fields" . + " where group_id = $group_id and org_id = $org_id and status = 1 and type > 0"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + while ($val = $res->fetchRow()) { + $this->fields[] = array('id' => $val['id'], 'type' => $val['type'], 'label' => $val['label'], 'required' => $val['required'], 'value' => ''); + if (CustomFields::ENTITY_TIME == $val['entity_type']) + $this->timeFields[] = $val; + else if (CustomFields::ENTITY_USER == $val['entity_type']) + $this->userFields[] = $val; + else if (CustomFields::ENTITY_PROJECT == $val['entity_type']) + $this->projectFields[] = $val; + } + } + } + + function insert($log_id, $field_id, $option_id, $value) { + global $user; + $mdb2 = getConnection(); - const TYPE_TEXT = 1; // A text field. - const TYPE_DROPDOWN = 2; // A dropdown field with pre-defined values. + $group_id = $user->getGroup(); + $org_id = $user->org_id; - var $fields = array(); // Array of custom fields for team. - var $options = array(); // Array of options for a dropdown custom field. + $sql = "insert into tt_custom_field_log (group_id, org_id, log_id, field_id, option_id, value)" . + " values($group_id, $org_id, $log_id, $field_id, " . $mdb2->quote($option_id) . ", " . $mdb2->quote($value) . ")"; + $affected = $mdb2->exec($sql); + return (!is_a($affected, 'PEAR_Error')); + } - // Constructor. - function __construct($team_id) { - $mdb2 = getConnection(); + function update($log_id, $field_id, $option_id, $value) { + if (!$field_id) + return true; // Nothing to update. - // Get fields. - $sql = "select id, type, label, required from tt_custom_fields where team_id = $team_id and status = 1 and type > 0"; - $res = $mdb2->query($sql); - if (!is_a($res, 'PEAR_Error')) { - while ($val = $res->fetchRow()) { - $this->fields[] = array('id'=>$val['id'],'type'=>$val['type'],'label'=>$val['label'],'required'=>$val['required'],'value'=>''); - } + // Remove older custom field values. + $res = $this->delete($log_id); + if (!$res) + return false; + + if (!$value && !$option_id) + return true; // Do not insert NULL values. + + return $this->insert($log_id, $field_id, $option_id, $value); } - // If we have a dropdown obtain options for it. - if ((count($this->fields) > 0) && ($this->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)) { + function delete($log_id) { + global $user; + $mdb2 = getConnection(); - $sql = "select id, value from tt_custom_field_options where field_id = ".$this->fields[0]['id']." order by value"; - $res = $mdb2->query($sql); - if (!is_a($res, 'PEAR_Error')) { - while ($val = $res->fetchRow()) { - $this->options[$val['id']] = $val['value']; - } - } + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $sql = "update tt_custom_field_log set status = null" . + " where log_id = $log_id and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + return (!is_a($affected, 'PEAR_Error')); } - } - function insert($log_id, $field_id, $option_id, $value) { + function get($log_id) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $sql = "select id, field_id, option_id, value from tt_custom_field_log" . + " where log_id = $log_id and group_id = $group_id and org_id = $org_id and status = 1"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + $fields = array(); + while ($val = $res->fetchRow()) { + $fields[] = $val; + } + return $fields; + } + return false; + } - $mdb2 = getConnection(); - $sql = "insert into tt_custom_field_log (log_id, field_id, option_id, value) values($log_id, $field_id, ".$mdb2->quote($option_id).", ".$mdb2->quote($value).")"; - $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); - } + // insertOption adds a new option to a custom field. + static function insertOption($field_id, $option_name) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + // Check if the option exists. + $id = 0; + $sql = "select id from tt_custom_field_options". + " where field_id = $field_id and group_id = $group_id and org_id = $org_id and value = ".$mdb2->quote($option_name). + " and status is not null"; + $res = $mdb2->query($sql); + if (is_a($res, 'PEAR_Error')) + return false; + if ($val = $res->fetchRow()) + $id = $val['id']; + + // Insert option. + if (!$id) { + $sql = "insert into tt_custom_field_options (group_id, org_id, field_id, value)" . + " values($group_id, $org_id, $field_id, " . $mdb2->quote($option_name) . ")"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + } - function update($log_id, $field_id, $option_id, $value) { - if (!$field_id) - return true; // Nothing to update. + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; - // Remove older custom field values, if any. - $res = $this->delete($log_id); - if (!$res) - return false; + return true; + } - if (!$value && !$option_id) - return true; // Do not insert NULL values. + // updateOption updates option name. + static function updateOption($id, $option_name) { + global $user; + $mdb2 = getConnection(); - return $this->insert($log_id, $field_id, $option_id, $value); - } + $group_id = $user->getGroup(); + $org_id = $user->org_id; - function delete($log_id) { + $sql = "update tt_custom_field_options set value = " . $mdb2->quote($option_name) . + " where id = $id and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; - $mdb2 = getConnection(); - $sql = "update tt_custom_field_log set status = NULL where log_id = $log_id"; - $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); - } + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; - function get($log_id) { - $fields = array(); + return true; + } - $mdb2 = getConnection(); - $sql = "select id, field_id, option_id, value from tt_custom_field_log where log_id = $log_id and status = 1"; - $res = $mdb2->query($sql); - if (!is_a($res, 'PEAR_Error')) { - while ($val = $res->fetchRow()) { - $fields[] = $val; - } - return $fields; + // delete Option deletes an option and all custom field log entries that used it. + static function deleteOption($id) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $field_id = CustomFields::getFieldIdForOption($id); + if (!$field_id) + return false; + + // Delete log entries with this option. TODO: why? Research impact. + $sql = "update tt_custom_field_log set status = null" . + " where field_id = $field_id and group_id = $group_id and org_id = $org_id and value = " . $mdb2->quote($id); + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Delete the option. + $sql = "update tt_custom_field_options set status = null" . + " where id = $id and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } - return false; - } - - // insertOption adds a new option to a custom field. - static function insertOption($field_id, $option_name) { - - $mdb2 = getConnection(); - - // Check if the option exists. - $id = 0; - $sql = "select id from tt_custom_field_options where field_id = $field_id and value = ".$mdb2->quote($option_name); - $res = $mdb2->query($sql); - if (is_a($res, 'PEAR_Error')) - return false; - if ($val = $res->fetchRow()) $id = $val['id']; - - // Insert option. - if (!$id) { - $sql = "insert into tt_custom_field_options (field_id, value) values($field_id, ".$mdb2->quote($option_name).")"; - $affected = $mdb2->exec($sql); - if (is_a($affected, 'PEAR_Error')) + + // getOptions returns an array of options for a custom field. + static function getOptions($field_id) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + // Get options. + $sql = "select id, value from tt_custom_field_options" . + " where field_id = $field_id and group_id = $group_id and org_id = $org_id and status = 1 order by value"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + $options = array(); + while ($val = $res->fetchRow()) { + $options[$val['id']] = $val['value']; + } + return $options; + } return false; } - return true; - } - - // updateOption updates option name. - static function updateOption($id, $option_name) { - - $mdb2 = getConnection(); - - $sql = "update tt_custom_field_options set value = ".$mdb2->quote($option_name)." where id = $id"; - $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); - } - - // delete Option deletes an option and all custom field log entries that used it. - static function deleteOption($id) { - global $user; - $mdb2 = getConnection(); - - $field_id = CustomFields::getFieldIdForOption($id); - - // First make sure that the field is ours. - $sql = "select team_id from tt_custom_fields where id = $field_id"; - $res = $mdb2->query($sql); - if (is_a($res, 'PEAR_Error')) - return false; - $val = $res->fetchRow(); - if ($user->team_id != $val['team_id']) - return false; - - // Delete log entries with this option. - $sql = "update tt_custom_field_log set status = NULL where field_id = $field_id and value = ".$mdb2->quote($id); - $affected = $mdb2->exec($sql); - if (is_a($affected, 'PEAR_Error')) - return false; - - // Delete the option. - $sql = "delete from tt_custom_field_options where id = $id"; - $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); - } - - // getOptions returns an array of options for a custom field. - static function getOptions($field_id) { - global $user; - $mdb2 = getConnection(); - $options = array(); - - // First make sure that the field is ours. - $sql = "select team_id from tt_custom_fields where id = $field_id"; - $res = $mdb2->query($sql); - if (is_a($res, 'PEAR_Error')) - return false; - $val = $res->fetchRow(); - if ($user->team_id != $val['team_id']) - return false; - - // Get options. - $sql = "select id, value from tt_custom_field_options where field_id = $field_id order by value"; - $res = $mdb2->query($sql); - if (!is_a($res, 'PEAR_Error')) { - while ($val = $res->fetchRow()) { - $options[$val['id']] = $val['value']; - } - return $options; + + // getOptionName returns an option name for a custom field. + static function getOptionName($id) { + global $user; + $mdb2 = getConnection(); + + $id = (int) $id; // Just in case. Better safe than sorry as it is used in sql. + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $sql = "select value from tt_custom_field_options" . + " where id = $id and group_id = $group_id and org_id = $org_id and status = 1"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + $val = $res->fetchRow(); + if (isset($val['value'])) + return $val['value']; + } + return null; + } + + // getFields returns an array of custom fields for group. + static function getFields() { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $fields = array(); + $sql = "select id, entity_type, type, label from tt_custom_fields" . + " where group_id = $group_id and org_id = $org_id and status = 1 and type > 0"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + while ($val = $res->fetchRow()) { + $fields[] = $val; + } + return $fields; + } + return false; } - return false; - } - - // getOptionName returns an option name for a custom field. - static function getOptionName($id) { - global $user; - $mdb2 = getConnection(); - - $field_id = CustomFields::getFieldIdForOption($id); - - // First make sure that the field is ours. - $sql = "select team_id from tt_custom_fields where id = $field_id"; - $res = $mdb2->query($sql); - if (is_a($res, 'PEAR_Error')) - return false; - $val = $res->fetchRow(); - if ($user->team_id != $val['team_id']) - return false; - - // Get option name. - $sql = "select value from tt_custom_field_options where id = $id"; - $res = $mdb2->query($sql); - if (!is_a($res, 'PEAR_Error')) { - $val = $res->fetchRow(); - $name = $val['value']; - return $name; + + // getField returns a custom field. + static function getField($id) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $sql = "select label, entity_type, type, required from tt_custom_fields" . + " where id = $id and group_id = $group_id and org_id = $org_id"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + $val = $res->fetchRow(); + if (!$val) + return false; + return $val; + } + return false; } - return false; - } - - // getFields returns an array of custom fields for team. - static function getFields() { - global $user; - $mdb2 = getConnection(); - - $fields = array(); - $sql = "select id, type, label from tt_custom_fields where team_id = $user->team_id and status = 1 and type > 0"; - $res = $mdb2->query($sql); - if (!is_a($res, 'PEAR_Error')) { - while ($val = $res->fetchRow()) { - $fields[] = array('id'=>$val['id'],'type'=>$val['type'],'label'=>$val['label']); + + // getFieldByName returns a custom field by name and its entity type. + static function getFieldByName($fieldName, $entityType) { + global $user; + $mdb2 = getConnection(); + + $entityType = (int) $entityType; // Just in case. + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $sql = "select label, type, required from tt_custom_fields" . + " where label = ". $mdb2->quote($fieldName) . " and entity_type = $entityType". + " and group_id = $group_id and org_id = $org_id and status is not null"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + $val = $res->fetchRow(); + if (isset($val)) + return $val; } - return $fields; + return null; } - return false; - } - - // getField returns a custom field. - static function getField($id) { - global $user; - $mdb2 = getConnection(); - - $sql = "select label, type, required from tt_custom_fields where id = $id and team_id = $user->team_id"; - $res = $mdb2->query($sql); - if (!is_a($res, 'PEAR_Error')) { - $val = $res->fetchRow(); - if (!$val) + + // getFieldIdForOption returns field id from an associated option id. + static function getFieldIdForOption($option_id) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $sql = "select field_id from tt_custom_field_options" . + " where id = $option_id and group_id = $group_id and org_id = $org_id"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + $val = $res->fetchRow(); + $field_id = $val['field_id']; + return $field_id; + } return false; - return $val; } - return false; - } - - // getFieldIdForOption returns field id from an associated option id. - static function getFieldIdForOption($option_id) { - $mdb2 = getConnection(); - - $sql = "select field_id from tt_custom_field_options where id = $option_id"; - $res = $mdb2->query($sql); - if (!is_a($res, 'PEAR_Error')) { - $val = $res->fetchRow(); - $field_id = $val['field_id']; - return $field_id; + + // The insertField inserts a custom field for group. + static function insertField($field_name, $entity_type, $field_type, $required) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + // TODO: refactor this. + if (!$entity_type) $entity_type = CustomFields::ENTITY_TIME; + + $sql = "insert into tt_custom_fields (group_id, org_id, entity_type, type, label, required, status)" . + " values($group_id, $org_id, $entity_type, $field_type, " . $mdb2->quote($field_name) . ", $required, 1)"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; + } + + // The updateField updates custom field for group. + static function updateField($id, $name, $type, $required) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $sql = "update tt_custom_fields set label = " . $mdb2->quote($name) . ", type = $type, required = $required" . + " where id = $id and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; + } + + // The deleteField deletes a custom field, its options and log entries for group. + static function deleteField($field_id) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + // Mark log entries as deleted. TODO: why are we doing this? Research impact. + // The impact is quite severe: an accidental delete of a custom field makes manual recovery problematic. + // Therefore, not doing this anymore as of Oct 7, 2021. + /* + $sql = "update tt_custom_field_log set status = null" . + " where field_id = $field_id and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + */ + + // Mark field options as deleted. + // Same comment as above applies. + // An accidental delete of a custom field makes manual recovery problematic. + // Therefore, not doing this anymore as of Oct 7, 2021. + /* + $sql = "update tt_custom_field_options set status = null" . + " where field_id = $field_id and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + */ + + // Mark custom field as deleted. + $sql = "update tt_custom_fields set status = null" . + " where id = $field_id and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + return false; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; + } + + // insertTimeFields - inserts time custom fields into tt_custom_field_log. + function insertTimeFields($log_id, $timeFields) { + foreach ($timeFields as $timeField) { + if (!$this->insertTimeField($log_id, $timeField)) + return false; + } + return true; + } + + // insertTimeField - inserts a single time custom field into tt_custom_field_log. + function insertTimeField($log_id, $timeField) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $field_id = $timeField['field_id']; + $type = $timeField['type']; + $option_id = $type == CustomFields::TYPE_DROPDOWN ? (int) $timeField['value'] : null; + $value = $type == CustomFields::TYPE_TEXT ? $timeField['value'] : null; + + // Do not insert empty fields. + if (($type == CustomFields::TYPE_DROPDOWN && !$option_id) || ($type == CustomFields::TYPE_TEXT && !$value)) + return true; + + // TODO: add a join to protect from bogus option_ids in post. + + $sql = "insert into tt_custom_field_log (group_id, org_id, log_id, field_id, option_id, value)" . + " values($group_id, $org_id, $log_id, $field_id, " . $mdb2->quote($option_id) . ", " . $mdb2->quote($value) . ")"; + $affected = $mdb2->exec($sql); + return (!is_a($affected, 'PEAR_Error')); + } + + // insertEntityFields - inserts entity custom fields into tt_entity_custom_fields. + function insertEntityFields($entity_type, $entity_id, $entityFields) { + foreach ($entityFields as $entityField) { + if (!$this->insertEntityField($entity_type, $entity_id, $entityField)) + return false; + } + return true; + } + + // insertEntityField - inserts a single entity custom field into tt_entity_custom_fields. + function insertEntityField($entity_type, $entity_id, $entityField) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $created = 'now(), ' . $mdb2->quote($_SERVER['REMOTE_ADDR']) . ', ' . $user->id; + + $field_id = (int) $entityField['field_id']; + $type = $entityField['type']; + $option_id = $type == CustomFields::TYPE_DROPDOWN ? (int) $entityField['value'] : null; + $value = $type == CustomFields::TYPE_TEXT ? $entityField['value'] : null; + + // Do not insert empty fields. + if (($type == CustomFields::TYPE_DROPDOWN && !$option_id) || ($type == CustomFields::TYPE_TEXT && !$value)) + return true; + + // TODO: add a join to protect from bogus option_ids in post. + $sql = "insert into tt_entity_custom_fields" . + " (group_id, org_id, entity_type, entity_id, field_id, option_id, value, created, created_ip, created_by)" . + " values($group_id, $org_id, $entity_type, $entity_id, $field_id, " . $mdb2->quote($option_id) . ", " . $mdb2->quote($value) . ", $created)"; + $affected = $mdb2->exec($sql); + return (!is_a($affected, 'PEAR_Error')); } - return false; - } - - // The insertField inserts a custom field for team. - static function insertField($field_name, $field_type, $required) { - global $user; - $mdb2 = getConnection(); - $sql = "insert into tt_custom_fields (team_id, type, label, required, status) values($user->team_id, $field_type, ".$mdb2->quote($field_name).", $required, 1)"; - $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); - } - - // The updateField updates custom field for team. - static function updateField($id, $name, $type, $required) { - global $user; - $mdb2 = getConnection(); - $sql = "update tt_custom_fields set label = ".$mdb2->quote($name).", type = $type, required = $required where id = $id and team_id = $user->team_id"; - $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); - } - - // The deleteField deletes a custom field, its options and log entries for team. - static function deleteField($field_id) { - - // Our overall intention is to keep the code simple and manageable. - // If a user wishes to delete a field, we will delete all its options and log entries. - // Otherwise we have to do conditional queries depending on field status (this complicates things). - - global $user; - $mdb2 = getConnection(); - - // First make sure that the field is ours so that we can safely delete it. - $sql = "select team_id from tt_custom_fields where id = $field_id"; - $res = $mdb2->query($sql); - if (is_a($res, 'PEAR_Error')) - return false; - $val = $res->fetchRow(); - if ($user->team_id != $val['team_id']) - return false; - - // Mark log entries as deleted. - $sql = "update tt_custom_field_log set status = NULL where field_id = $field_id"; - $affected = $mdb2->exec($sql); - if (is_a($affected, 'PEAR_Error')) - return false; - - // Delete field options. - $sql = "delete from tt_custom_field_options where field_id = $field_id"; - $affected = $mdb2->exec($sql); - if (is_a($affected, 'PEAR_Error')) - return false; - - // Delete the field. - $sql = "delete from tt_custom_fields where id = $field_id and team_id = $user->team_id"; - $affected = $mdb2->exec($sql); - return (!is_a($affected, 'PEAR_Error')); - } + + // updateEntityFields - updates entity custom fields in tt_entity_custom_fields table + // by doing a delete followed up by an insert. + function updateEntityFields($entity_type, $entity_id, $entityFields) { + $result = $this->deleteEntityFields($entity_type, $entity_id); + if (!$result) + return false; + + return $this->insertEntityFields($entity_type, $entity_id, $entityFields); + } + + // deleteEntityFields - deletes entity custom fields (permanently). + // Note: deleting, rather than marking fields deleted is on purpose + // because we want to keep the table small after multiple entity edits. + function deleteEntityFields($entity_type, $entity_id) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $sql = "delete from tt_entity_custom_fields" . + " where entity_type = $entity_type and entity_id = $entity_id" . + " and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + return (!is_a($affected, 'PEAR_Error')); + } + + // deleteTimeFields - deletes time custom fields (permanently). + // Note: deleting, rather than marking fields deleted is on purpose + // because we want to keep the table small after multiple edits. + function deleteTimeFields($log_id) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $sql = "delete from tt_custom_field_log" . + " where log_id = $log_id" . + " and group_id = $group_id and org_id = $org_id"; + $affected = $mdb2->exec($sql); + return (!is_a($affected, 'PEAR_Error')); + } + + // getEntityFieldValue - obtains entity custom field value from the database. + function getEntityFieldValue($entity_type, $entity_id, $field_id, $type) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $sql = "select option_id, value from tt_entity_custom_fields" . + " where entity_type = $entity_type and entity_id = $entity_id" . + " and field_id = $field_id" . + " and group_id = $group_id and org_id = $org_id and status = 1"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + if ($val = $res->fetchRow()) { + if (CustomFields::TYPE_DROPDOWN == $type) + return $val['option_id']; + if (CustomFields::TYPE_TEXT == $type) + return $val['value']; + } + } + return null; + } + + // getTimeFieldValue - obtains time custom field value from the database. + function getTimeFieldValue($log_id, $field_id, $type) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $sql = "select option_id, value from tt_custom_field_log" . + " where log_id = $log_id" . + " and field_id = $field_id" . + " and group_id = $group_id and org_id = $org_id and status = 1"; + $res = $mdb2->query($sql); + if (!is_a($res, 'PEAR_Error')) { + if ($val = $res->fetchRow()) { + if (CustomFields::TYPE_DROPDOWN == $type) + return $val['option_id']; + if (CustomFields::TYPE_TEXT == $type) + return $val['value']; + } + } + return null; + } + + // updateTimeFields - updates time custom fields in tt_custom_field_log table + // by doing a delete followed up by an insert. + function updateTimeFields($log_id, $timeFields) { + $result = $this->deleteTimeFields($log_id); + if (!$result) + return false; + + return $this->insertTimeFields($log_id, $timeFields); + } + } diff --git a/plugins/MonthlyQuota.class.php b/plugins/MonthlyQuota.class.php index a42b78242..f37cd7885 100644 --- a/plugins/MonthlyQuota.class.php +++ b/plugins/MonthlyQuota.class.php @@ -1,59 +1,46 @@ db = getConnection(); global $user; - $this->team_id = $user->team_id; + $this->group_id = $user->getGroup(); + $this->org_id = $user->org_id; } // update - deletes a quota, then inserts a new one. - public function update($year, $month, $quota) { - $teamId = $this->team_id; - $deleteSql = "DELETE FROM tt_monthly_quotas WHERE year = $year AND month = $month AND team_id = $teamId"; + public function update($year, $month, $minutes) { + $deleteSql = "delete from tt_monthly_quotas". + " where year = $year and month = $month and group_id = $this->group_id and org_id = $this->org_id"; $this->db->exec($deleteSql); - if ($quota){ - $insertSql = "INSERT INTO tt_monthly_quotas (team_id, year, month, quota) values ($teamId, $year, $month, $quota)"; + if ($minutes){ + $insertSql = "insert into tt_monthly_quotas (group_id, org_id, year, month, minutes)". + " values ($this->group_id, $this->org_id, $year, $month, $minutes)"; $affected = $this->db->exec($insertSql); - return (!is_a($affected, 'PEAR_Error')); + if (is_a($affected, 'PEAR_Error')) + return false; } + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + return true; } // get - obtains either a single month quota or an array of quotas for an entire year. - public function get($year, $month) { + // Month starts with 1 for January, not 0. + public function get($year, $month = null) { if (is_null($month)){ return $this->getMany($year); } @@ -62,8 +49,8 @@ public function get($year, $month) { // getSingle - obtains a quota for a single month. private function getSingle($year, $month) { - $teamId = $this->team_id; - $sql = "SELECT quota FROM tt_monthly_quotas WHERE year = $year AND month = $month AND team_id = $teamId"; + $sql = "select minutes from tt_monthly_quotas". + " where year = $year and month = $month and group_id = $this->group_id and org_id = $this->org_id"; $reader = $this->db->query($sql); if (is_a($reader, 'PEAR_Error')) { return false; @@ -71,18 +58,47 @@ private function getSingle($year, $month) { $row = $reader->fetchRow(); if ($row) - return $row['quota']; + return $row['minutes']; // If we did not find a record, return a calculated monthly quota. $numWorkdays = $this->getNumWorkdays($month, $year); global $user; - return $numWorkdays * $user->workday_hours; + return $numWorkdays * $user->getWorkdayMinutes(); + } + + // getUserQuota - obtains a quota for user for a single month. + // This quota is adjusted by quota_percent value for user. + public function getUserQuota($year, $month) { + global $user; + + $minutes = $this->getSingle($year, $month); + $userMinutes = (int) $minutes * $user->getQuotaPercent() / 100; + return $userMinutes; + } + + // getUserQuotaFrom1st - obtains a quota for user + // from 1st of the month up to and inclusive of $selected_date. + public function getUserQuotaFrom1st($selected_date) { + // TODO: we may need a better algorithm here. Review. + $monthQuotaMinutes = $this->getUserQuota($selected_date->year, $selected_date->month); + $workdaysInMonth = $this->getNumWorkdays($selected_date->month, $selected_date->year); + + // Iterate from 1st up to selected date. + $workdaysFrom1st = 0; + for ($i = 1; $i <= $selected_date->day; $i++) { + $date = ttTimeHelper::dateInDatabaseFormat($selected_date->year, $selected_date->month, $i); + if (!ttTimeHelper::isWeekend($date) && !ttTimeHelper::isHoliday($date)) { + $workdaysFrom1st++; + } + } + $quotaMinutesFrom1st = (int) ($monthQuotaMinutes * $workdaysFrom1st / $workdaysInMonth); + return $quotaMinutesFrom1st; } - // getMany - returns an array of quotas for a given year for team. + // getMany - returns an array of quotas for a given year for group. private function getMany($year){ - $teamId = $this->team_id; - $sql = "SELECT month, quota FROM tt_monthly_quotas WHERE year = $year AND team_id = $teamId"; + $sql = "select month, minutes from tt_monthly_quotas". + " where year = $year and group_id = $this->group_id and org_id = $this->org_id"; $result = array(); $res = $this->db->query($sql); if (is_a($res, 'PEAR_Error')) { @@ -90,7 +106,7 @@ private function getMany($year){ } while ($val = $res->fetchRow()) { - $result[$val['month']] = $val['quota']; + $result[$val['month']] = $val['minutes']; } return $result; @@ -99,16 +115,64 @@ private function getMany($year){ // getNumWorkdays returns a number of work days in a given month. private function getNumWorkdays($month, $year) { - $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year); // Number of calendar days in month. + $daysInMonth = date('t', mktime(0, 0, 0, $month, 1, $year)); $workdaysInMonth = 0; // Iterate through the entire month. for ($i = 1; $i <= $daysInMonth; $i++) { $date = "$year-$month-$i"; + $date = ttTimeHelper::dateInDatabaseFormat($year, $month, $i); if (!ttTimeHelper::isWeekend($date) && !ttTimeHelper::isHoliday($date)) { $workdaysInMonth++; } } return $workdaysInMonth; } + + // isValidQuota validates a localized value as an hours quota string (in hours and minutes). + public function isValidQuota($value) { + + if (strlen($value) == 0 || !isset($value)) return true; + + if (preg_match('/^[0-9]{1,3}h?$/', $value )) { // 000 - 999 + return true; + } + + if (preg_match('/^[0-9]{1,3}:[0-5][0-9]$/', $value )) { // 000:00 - 999:59 + return true; + } + + global $user; + $localizedPattern = '/^([0-9]{1,3})?['.$user->getDecimalMark().'][0-9]{1,4}h?$/'; + if (preg_match($localizedPattern, $value )) { // decimal values like 000.5, 999.25h, ... .. 999.9999h (or with comma) + return true; + } + + return false; + } + + // quotaToFloat converts a valid quota value to a float. + public function quotaToFloat($value) { + + if (preg_match('/^[0-9]{1,3}h?$/', $value )) { // 000 - 999 + return (float) $value; + } + + if (preg_match('/^[0-9]{1,3}:[0-5][0-9]$/', $value )) { // 000:00 - 999:59 + $minutes = ttTimeHelper::toMinutes($value); + return ($minutes / 60.0); + } + + global $user; + $localizedPattern = '/^([0-9]{1,3})?['.$user->getDecimalMark().'][0-9]{1,4}h?$/'; + if (preg_match($localizedPattern, $value )) { // decimal values like 000.5, 999.25h, ... .. 999.9999h (or with comma) + // Strip optional h in the end. + $value = trim($value, 'h'); + if ($user->getDecimalMark() == ',') + $value = str_replace(',', '.', $value); + return (float) $value; + } + + return null; + } } diff --git a/predefined_expense_add.php b/predefined_expense_add.php index 76fbb221e..08e395ce2 100644 --- a/predefined_expense_add.php +++ b/predefined_expense_add.php @@ -1,68 +1,49 @@ isPluginEnabled('ex')) { +// Access checks. +if (!ttAccessAllowed('manage_advanced_settings')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('ex')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. +$cl_name = $cl_cost = null; if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_cost = trim($request->getParameter('cost')); } $form = new Form('predefinedExpenseForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 250px;','value'=>$cl_name)); -$form->addInput(array('type'=>'text','maxlength'=>'40','name'=>'cost','style'=>'width: 100px;','value'=>$cl_cost)); -$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add'))); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'text','class'=>'text-field-with-hint','maxlength'=>'40','name'=>'cost','value'=>$cl_cost)); +$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidFloat($cl_cost)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.cost')); + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidFloat($cl_cost)) $err->add($i18n->get('error.field'), $i18n->get('label.cost')); if ($err->no()) { if (ttPredefinedExpenseHelper::insert(array( - 'team_id' => $user->team_id, 'name' => $cl_name, 'cost' => $cl_cost))) { header('Location: predefined_expenses.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.add_predefined_expense')); +$smarty->assign('title', $i18n->get('title.add_predefined_expense')); $smarty->assign('content_page_name', 'predefined_expense_add.tpl'); $smarty->display('index.tpl'); diff --git a/predefined_expense_delete.php b/predefined_expense_delete.php index 1c8a0fed4..e965b0be2 100644 --- a/predefined_expense_delete.php +++ b/predefined_expense_delete.php @@ -1,60 +1,43 @@ isPluginEnabled('ex')) { +// Access checks. +if (!ttAccessAllowed('manage_advanced_settings')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('ex')) { + header('Location: feature_disabled.php'); + exit(); +} +$predefined_expense_id = (int)$request->getParameter('id'); +$predefined_expense = ttPredefinedExpenseHelper::get($predefined_expense_id); +if (!$predefined_expense) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + -$cl_predefined_expense_id = (int)$request->getParameter('id'); -$predefined_expense = ttPredefinedExpenseHelper::get($cl_predefined_expense_id); $predefined_expense_to_delete = $predefined_expense['name']; $form = new Form('predefinedExpenseDeleteForm'); -$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_predefined_expense_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$predefined_expense_id)); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); if ($request->isPost()) { if ($request->getParameter('btn_delete')) { - if(ttPredefinedExpenseHelper::get($cl_predefined_expense_id)) { - if (ttPredefinedExpenseHelper::delete($cl_predefined_expense_id)) { - header('Location: predefined_expenses.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); + if (ttPredefinedExpenseHelper::delete($predefined_expense_id)) { + header('Location: predefined_expenses.php'); + exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } elseif ($request->getParameter('btn_cancel')) { header('Location: predefined_expenses.php'); exit(); @@ -64,6 +47,6 @@ $smarty->assign('predefined_expense_to_delete', $predefined_expense_to_delete); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.predefinedExpenseDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->getKey('title.delete_predefined_expense')); +$smarty->assign('title', $i18n->get('title.delete_predefined_expense')); $smarty->assign('content_page_name', 'predefined_expense_delete.tpl'); $smarty->display('index.tpl'); diff --git a/predefined_expense_edit.php b/predefined_expense_edit.php index 83f3fddf2..8f4750245 100644 --- a/predefined_expense_edit.php +++ b/predefined_expense_edit.php @@ -1,76 +1,59 @@ isPluginEnabled('ex')) { +// Access checks. +if (!ttAccessAllowed('manage_advanced_settings')) { header('Location: access_denied.php'); exit(); } - -$predefined_expense_id = (int) $request->getParameter('id'); +if (!$user->isPluginEnabled('ex')) { + header('Location: feature_disabled.php'); + exit(); +} +$predefined_expense_id = (int)$request->getParameter('id'); +$predefined_expense = ttPredefinedExpenseHelper::get($predefined_expense_id); +if (!$predefined_expense) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_cost = trim($request->getParameter('cost')); } else { - $predefined_expense = ttPredefinedExpenseHelper::get($predefined_expense_id); $cl_name = $predefined_expense['name']; $cl_cost = $predefined_expense['cost']; } $form = new Form('predefinedExpenseForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$predefined_expense_id)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 250px;','value'=>$cl_name)); -$form->addInput(array('type'=>'text','maxlength'=>'40','name'=>'cost','style'=>'width: 100px;','value'=>$cl_cost)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.submit'))); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'text','class'=>'text-field-with-hint','maxlength'=>'40','name'=>'cost','value'=>$cl_cost)); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.submit'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidFloat($cl_cost)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.cost')); + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidFloat($cl_cost)) $err->add($i18n->get('error.field'), $i18n->get('label.cost')); if ($err->no()) { if (ttPredefinedExpenseHelper::update(array( 'id' => $predefined_expense_id, - 'team_id' => $user->team_id, 'name' => $cl_name, 'cost' => $cl_cost))) { header('Location: predefined_expenses.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.edit_predefined_expense')); +$smarty->assign('title', $i18n->get('title.edit_predefined_expense')); $smarty->assign('content_page_name', 'predefined_expense_edit.tpl'); $smarty->display('index.tpl'); diff --git a/predefined_expenses.php b/predefined_expenses.php index 4fa6d403c..9f94bd1cc 100644 --- a/predefined_expenses.php +++ b/predefined_expenses.php @@ -1,40 +1,21 @@ isPluginEnabled('ex')) { +// Access checks. +if (!ttAccessAllowed('manage_advanced_settings')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('ex')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. $form = new Form('predefinedExpensesForm'); @@ -45,12 +26,12 @@ exit(); } } else { - $form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add'))); - $predefinedExpenses = ttTeamHelper::getPredefinedExpenses($user->team_id); + $form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); + $predefinedExpenses = ttGroupHelper::getPredefinedExpenses(); } $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('predefined_expenses', $predefinedExpenses); -$smarty->assign('title', $i18n->getKey('title.predefined_expenses')); +$smarty->assign('title', $i18n->get('title.predefined_expenses')); $smarty->assign('content_page_name', 'predefined_expenses.tpl'); $smarty->display('index.tpl'); diff --git a/profile_edit.php b/profile_edit.php index 3bf547acb..3958a4018 100644 --- a/profile_edit.php +++ b/profile_edit.php @@ -1,44 +1,28 @@ exists()) { + header('Location: access_denied.php'); // No users in subgroup. + exit(); +} +// End of access checks. -if (!defined('CURRENCY_DEFAULT')) define('CURRENCY_DEFAULT', '$'); -$can_change_login = $user->canManageTeam(); +$can_manage_account = $user->behalfGroup ? $user->can('manage_subgroups') : $user->can('manage_own_account'); +if ($user->behalf_id) $user_details = $user->getUserDetails($user->behalf_id); +$current_login = $user->behalf_id ? $user_details['login'] : $user->login; +$cl_name = $cl_login = $cl_password1 = $cl_password2 = $cl_email = null; if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_login = trim($request->getParameter('login')); @@ -47,233 +31,64 @@ $cl_password2 = $request->getParameter('password2'); } $cl_email = trim($request->getParameter('email')); - - if ($user->canManageTeam()) { - $cl_team = trim($request->getParameter('team_name')); - $cl_address = trim($request->getParameter('address')); - $cl_currency = trim($request->getParameter('currency')); - if (!$cl_currency) $cl_currency = CURRENCY_DEFAULT; - $cl_lang = $request->getParameter('lang'); - $cl_decimal_mark = $request->getParameter('decimal_mark'); - $cl_custom_format_date = $request->getParameter('format_date'); - $cl_custom_format_time = $request->getParameter('format_time'); - $cl_start_week = $request->getParameter('start_week'); - $cl_tracking_mode = $request->getParameter('tracking_mode'); - $cl_record_type = $request->getParameter('record_type'); - $cl_uncompleted_indicators = $request->getParameter('uncompleted_indicators'); - $cl_charts = $request->getParameter('charts'); - $cl_clients = $request->getParameter('clients'); - $cl_client_required = $request->getParameter('client_required'); - $cl_invoices = $request->getParameter('invoices'); - $cl_custom_fields = $request->getParameter('custom_fields'); - $cl_expenses = $request->getParameter('expenses'); - $cl_tax_expenses = $request->getParameter('tax_expenses'); - $cl_notifications = $request->getParameter('notifications'); - $cl_locking = $request->getParameter('locking'); - $cl_quotas = $request->getParameter('quotas'); - } } else { - $cl_name = $user->name; - $cl_login = $user->login; - $cl_email = $user->email; - if ($user->canManageTeam()) { - $cl_team = $user->team; - $cl_address = $user->address; - $cl_currency = ($user->currency == ''? CURRENCY_DEFAULT : $user->currency); - $cl_lang = $user->lang; - $cl_decimal_mark = $user->decimal_mark; - $cl_custom_format_date = $user->date_format; - $cl_custom_format_time = $user->time_format; - $cl_start_week = $user->week_start; - $cl_tracking_mode = $user->tracking_mode; - $cl_record_type = $user->record_type; - $cl_uncompleted_indicators = $user->uncompleted_indicators; - - // Which plugins do we have enabled? - $plugins = explode(',', $user->plugins); - $cl_charts = in_array('ch', $plugins); - $cl_clients = in_array('cl', $plugins); - $cl_client_required = in_array('cm', $plugins); - $cl_invoices = in_array('iv', $plugins); - $cl_custom_fields = in_array('cf', $plugins); - $cl_expenses = in_array('ex', $plugins); - $cl_tax_expenses = in_array('et', $plugins); - $cl_notifications = in_array('no', $plugins); - $cl_locking = in_array('lk', $plugins); - $cl_quotas = in_array('mq', $plugins); + if ($user->behalf_id) { + $cl_name = $user_details['name']; + $cl_login = $user_details['login']; + $cl_email = $user_details['email']; + } else { + $cl_name = $user->name; + $cl_login = $user->login; + $cl_email = $user->email; } } $form = new Form('profileForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login,'enable'=>$can_change_login)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name,'enable'=>$can_manage_account)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login,'enable'=>$can_manage_account)); if (!$auth->isPasswordExternal()) { - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password1','aspassword'=>true,'value'=>$cl_password1)); - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password2','aspassword'=>true,'value'=>$cl_password2)); -} -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','value'=>$cl_email,'enable'=>$can_change_login)); -if ($user->canManageTeam()) { - $form->addInput(array('type'=>'text','maxlength'=>'200','name'=>'team_name','value'=>$cl_team)); - $form->addInput(array('type'=>'textarea','name'=>'address','maxlength'=>'255','style'=>'width: 350px;','cols'=>'55','rows'=>'4','value'=>$cl_address)); - $form->addInput(array('type'=>'text','maxlength'=>'7','name'=>'currency','value'=>$cl_currency)); - $DECIMAL_MARK_OPTIONS = array(array('id'=>'.','name'=>'.'),array('id'=>',','name'=>',')); - $form->addInput(array('type'=>'combobox','name'=>'decimal_mark','style'=>'width: 150px','data'=>$DECIMAL_MARK_OPTIONS,'datakeys'=>array('id','name'),'value'=>$cl_decimal_mark, - 'onchange'=>'adjustDecimalPreview()')); - // Prepare an array of available languages. - $lang_files = I18n::getLangFileList(); - foreach ($lang_files as $lfile) { - $content = file(RESOURCE_DIR."/".$lfile); - $lname = ''; - foreach ($content as $line) { - if (strstr($line, 'i18n_language')) { - $a = explode('=', $line); - $lname = trim(str_replace(';','',str_replace("'","",$a[1]))); - break; - } - } - unset($content); - $longname_lang[] = array('id'=>I18n::getLangFromFilename($lfile),'name'=>$lname); - } - $longname_lang = mu_sort($longname_lang, 'name'); - $form->addInput(array('type'=>'combobox','name'=>'lang','style'=>'width: 150px','data'=>$longname_lang,'datakeys'=>array('id','name'),'value'=>$cl_lang)); - $DATE_FORMAT_OPTIONS = array( - array('id'=>'%Y-%m-%d','name'=>'Y-m-d'), - array('id'=>'%m/%d/%Y','name'=>'m/d/Y'), - array('id'=>'%d.%m.%Y','name'=>'d.m.Y'), - array('id'=>'%d.%m.%Y %a','name'=>'d.m.Y a')); - $form->addInput(array('type'=>'combobox','name'=>'format_date','style'=>'width: 150px;','data'=>$DATE_FORMAT_OPTIONS,'datakeys'=>array('id','name'),'value'=>$cl_custom_format_date, - 'onchange'=>'MakeFormatPreview("date_format_preview", this);')); - $TIME_FORMAT_OPTIONS = array( - array('id'=>'%H:%M','name'=>$i18n->getKey('form.profile.24_hours')), - array('id'=>'%I:%M %p','name'=>$i18n->getKey('form.profile.12_hours'))); - $form->addInput(array('type'=>'combobox','name'=>'format_time','style'=>'width: 150px;','data'=>$TIME_FORMAT_OPTIONS,'datakeys'=>array('id','name'),'value'=>$cl_custom_format_time, - 'onchange'=>'MakeFormatPreview("time_format_preview", this);')); - - // Prepare week start choices. - $week_start_options = array(); - foreach ($i18n->weekdayNames as $id => $week_dn) { - $week_start_options[] = array('id' => $id, 'name' => $week_dn); - } - $form->addInput(array('type'=>'combobox','name'=>'start_week','style'=>'width: 150px;','data'=>$week_start_options,'datakeys'=>array('id','name'),'value'=>$cl_start_week)); - - // Prepare tracking mode choices. - $tracking_mode_options = array(); - $tracking_mode_options[MODE_TIME] = $i18n->getKey('form.profile.mode_time'); - $tracking_mode_options[MODE_PROJECTS] = $i18n->getKey('form.profile.mode_projects'); - $tracking_mode_options[MODE_PROJECTS_AND_TASKS] = $i18n->getKey('form.profile.mode_projects_and_tasks'); - $form->addInput(array('type'=>'combobox','name'=>'tracking_mode','style'=>'width: 150px;','data'=>$tracking_mode_options,'value'=>$cl_tracking_mode)); - - // Prepare record type choices. - $record_type_options = array(); - $record_type_options[TYPE_ALL] = $i18n->getKey('form.profile.type_all'); - $record_type_options[TYPE_START_FINISH] = $i18n->getKey('form.profile.type_start_finish'); - $record_type_options[TYPE_DURATION] = $i18n->getKey('form.profile.type_duration'); - $form->addInput(array('type'=>'combobox','name'=>'record_type','style'=>'width: 150px;','data'=>$record_type_options,'value'=>$cl_record_type)); - - // Prepare uncompleted indicators choices. - $uncompleted_indicators_options = array(); - $uncompleted_indicators_options[UNCOMPLETED_INDICATORS_NONE] = $i18n->getKey('form.profile.uncompleted_indicators_none'); - $uncompleted_indicators_options[UNCOMPLETED_INDICATORS] = $i18n->getKey('form.profile.uncompleted_indicators_show'); - $form->addInput(array('type'=>'combobox','name'=>'uncompleted_indicators','style'=>'width: 150px;','data'=>$uncompleted_indicators_options,'value'=>$cl_uncompleted_indicators)); - - $form->addInput(array('type'=>'checkbox','name'=>'charts','data'=>1,'value'=>$cl_charts)); - $form->addInput(array('type'=>'checkbox','name'=>'clients','data'=>1,'value'=>$cl_clients,'onchange'=>'handlePluginCheckboxes()')); - $form->addInput(array('type'=>'checkbox','name'=>'client_required','data'=>1,'value'=>$cl_client_required)); - - $form->addInput(array('type'=>'checkbox','name'=>'invoices','data'=>1,'value'=>$cl_invoices)); - $form->addInput(array('type'=>'checkbox','name'=>'custom_fields','data'=>1,'value'=>$cl_custom_fields,'onchange'=>'handlePluginCheckboxes()')); - $form->addInput(array('type'=>'checkbox','name'=>'expenses','data'=>1,'value'=>$cl_expenses,'onchange'=>'handlePluginCheckboxes()')); - $form->addInput(array('type'=>'checkbox','name'=>'tax_expenses','data'=>1,'value'=>$cl_tax_expenses)); - $form->addInput(array('type'=>'checkbox','name'=>'notifications','data'=>1,'value'=>$cl_notifications,'onchange'=>'handlePluginCheckboxes()')); - $form->addInput(array('type'=>'checkbox','name'=>'locking','data'=>1,'value'=>$cl_locking,'onchange'=>'handlePluginCheckboxes()')); - $form->addInput(array('type'=>'checkbox','name'=>'quotas','data'=>1,'value'=>$cl_quotas,'onchange'=>'handlePluginCheckboxes()')); + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'password1','value'=>$cl_password1)); + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'password2','value'=>$cl_password2)); } -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','value'=>$cl_email,'enable'=>$can_manage_account)); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name')); - if ($can_change_login) { - if (!ttValidString($cl_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.person_name')); + if (!ttValidString($cl_login)) $err->add($i18n->get('error.field'), $i18n->get('label.login')); + + // New login must be unique. + if ($cl_login != $current_login && ttUserHelper::getUserByLogin($cl_login)) + $err->add($i18n->get('error.user_exists')); - // New login must be unique. - if ($cl_login != $user->login && ttUserHelper::getUserByLogin($cl_login)) - $err->add($i18n->getKey('error.user_exists')); - } if (!$auth->isPasswordExternal() && ($cl_password1 || $cl_password2)) { - if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); + if (!ttValidString($cl_password1)) $err->add($i18n->get('error.field'), $i18n->get('label.password')); + if (!ttValidString($cl_password2)) $err->add($i18n->get('error.field'), $i18n->get('label.confirm_password')); if ($cl_password1 !== $cl_password2) - $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } - if (!ttValidEmail($cl_email, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - if ($user->canManageTeam()) { - if (!ttValidString($cl_team, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.team_name')); - if (!ttValidString($cl_address, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.address')); - if (!ttValidString($cl_currency, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.currency')); + $err->add($i18n->get('error.not_equal'), $i18n->get('label.password'), $i18n->get('label.confirm_password')); + // Check password complexity. + if (!ttCheckPasswordComplexity($cl_password1)) + $err->add($i18n->get('error.weak_password')); } + if (!ttValidEmail($cl_email, true)) $err->add($i18n->get('error.field'), $i18n->get('label.email')); // Finished validating user input. if ($err->no()) { - $update_result = true; - if ($user->canManageTeam()) { - - // Prepare plugins string. - if ($cl_charts) - $plugins .= ',ch'; - if ($cl_clients) - $plugins .= ',cl'; - if ($cl_client_required) - $plugins .= ',cm'; - if ($cl_invoices) - $plugins .= ',iv'; - if ($cl_custom_fields) - $plugins .= ',cf'; - if ($cl_expenses) - $plugins .= ',ex'; - if ($cl_tax_expenses) - $plugins .= ',et'; - if ($cl_notifications) - $plugins .= ',no'; - if ($cl_locking) - $plugins .= ',lk'; - if ($cl_quotas) - $plugins .= ',mq'; - $plugins = trim($plugins, ','); - - $update_result = ttTeamHelper::update($user->team_id, array( - 'name' => $cl_team, - 'address' => $cl_address, - 'currency' => $cl_currency, - 'lang' => $cl_lang, - 'decimal_mark' => $cl_decimal_mark, - 'date_format' => $cl_custom_format_date, - 'time_format' => $cl_custom_format_time, - 'week_start' => $cl_start_week, - 'tracking_mode' => $cl_tracking_mode, - 'record_type' => $cl_record_type, - 'uncompleted_indicators' => $cl_uncompleted_indicators, - 'plugins' => $plugins)); - } - if ($update_result) { - $update_result = ttUserHelper::update($user->id, array( - 'name' => $cl_name, - 'login' => $cl_login, - 'password' => $cl_password1, - 'email' => $cl_email, - 'status' => ACTIVE)); - } + $fields = $can_manage_account ? + array('name'=>$cl_name,'login'=>$cl_login,'password'=>$cl_password1,'email'=>$cl_email) : + array('password'=>$cl_password1); + $update_result = ttUserHelper::update($user->getUser(), $fields); if ($update_result) { header('Location: time.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } // isPost $smarty->assign('auth_external', $auth->isPasswordExternal()); $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="handlePluginCheckboxes()"'); -$smarty->assign('title', $i18n->getKey('title.profile')); +$smarty->assign('title', $i18n->get('title.profile')); $smarty->assign('content_page_name', 'profile_edit.tpl'); $smarty->display('index.tpl'); diff --git a/project_add.php b/project_add.php index fe46a6bd4..859fdc259 100644 --- a/project_add.php +++ b/project_add.php @@ -1,55 +1,59 @@ tracking_mode && MODE_PROJECTS_AND_TASKS != $user->tracking_mode)) { +// Access checks. +if (!ttAccessAllowed('manage_projects')) { header('Location: access_denied.php'); exit(); } +if (MODE_PROJECTS != $user->getTrackingMode() && MODE_PROJECTS_AND_TASKS != $user->getTrackingMode()) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. + +// Use custom fields plugin if it is enabled. +if ($user->isPluginEnabled('cf')) { + require_once('plugins/CustomFields.class.php'); + $custom_fields = new CustomFields(); + $smarty->assign('custom_fields', $custom_fields); +} -$users = ttTeamHelper::getActiveUsers(); +$showFiles = $user->isPluginEnabled('at'); +$users = ttGroupHelper::getActiveUsers(); foreach ($users as $user_item) $all_users[$user_item['id']] = $user_item['name']; -$tasks = ttTeamHelper::getActiveTasks($user->team_id); +$tasks = ttGroupHelper::getActiveTasks(); foreach ($tasks as $task_item) $all_tasks[$task_item['id']] = $task_item['name']; +$show_tasks = MODE_PROJECTS_AND_TASKS == $user->getTrackingMode() && count($tasks) > 0; +$cl_name = $cl_description = ''; if ($request->isPost()) { $cl_name = trim($request->getParameter('project_name')); $cl_description = trim($request->getParameter('description')); $cl_users = $request->getParameter('users', array()); $cl_tasks = $request->getParameter('tasks', array()); + // If we have project custom fields - collect input. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $control_name = 'project_field_'.$projectField['id']; + $projectCustomFields[$projectField['id']] = array('field_id' => $projectField['id'], + 'control_name' => $control_name, + 'label' => $projectField['label'], + 'type' => $projectField['type'], + 'required' => $projectField['required'], + 'value' => trim($request->getParameter($control_name))); + } + } } else { foreach ($users as $user_item) $cl_users[] = $user_item['id']; @@ -58,38 +62,78 @@ } $form = new Form('projectForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'project_name','style'=>'width: 250px;','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 250px; height: 40px;','value'=>$cl_description)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'project_name','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +// If we have custom fields - add controls for them. +if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + $form->addInput(array('type'=>'text','name'=>$field_name,'value'=>$projectCustomFields[$projectField['id']]['value'])); + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + $form->addInput(array('type'=>'combobox','name'=>$field_name, + 'data'=>CustomFields::getOptions($projectField['id']), + 'value'=>$projectCustomFields[$projectField['id']]['value'], + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + } + } +} +if ($showFiles) + $form->addInput(array('type'=>'upload','name'=>'newfile','value'=>$i18n->get('button.submit'))); $form->addInput(array('type'=>'checkboxgroup','name'=>'users','data'=>$all_users,'layout'=>'H','value'=>$cl_users)); -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) +if ($show_tasks) $form->addInput(array('type'=>'checkboxgroup','name'=>'tasks','data'=>$all_tasks,'layout'=>'H','value'=>$cl_tasks)); -$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->getKey('button.add'))); +$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description')); + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + // Validate input in project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($projectCustomFields as $projectField) { + // Validation is the same for text and dropdown fields. + if (!ttValidString($projectField['value'], !$projectField['required'])) $err->add($i18n->get('error.field'), htmlspecialchars($projectField['label'])); + } + } + if (!ttGroupHelper::validateCheckboxGroupInput($cl_users, 'tt_users')) $err->add($i18n->get('error.field'), $i18n->get('label.users')); + if (!ttGroupHelper::validateCheckboxGroupInput($cl_tasks, 'tt_tasks')) $err->add($i18n->get('error.field'), $i18n->get('label.tasks')); if ($err->no()) { if (!ttProjectHelper::getProjectByName($cl_name)) { - if (ttProjectHelper::insert(array( - 'team_id' => $user->team_id, - 'name' => $cl_name, + $project_id = ttProjectHelper::insert(array('name' => $cl_name, 'description' => $cl_description, 'users' => $cl_users, 'tasks' => $cl_tasks, - 'status' => ACTIVE))) { - header('Location: projects.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); + 'status' => ACTIVE)); + // Insert project custom fields if we have them. + $result = true; + if ($project_id && isset($custom_fields) && $custom_fields->projectFields) { + $result = $custom_fields->insertEntityFields(CustomFields::ENTITY_PROJECT, $project_id, $projectCustomFields); + } + // Put a new file in storage if we have it. + if ($project_id && $showFiles && $_FILES['newfile']['name']) { + $fileHelper = new ttFileHelper($err); + $fields = array('entity_type'=>'project', + 'entity_id' => $project_id, + 'file_name' => $_FILES['newfile']['name']); + $fileHelper->putFile($fields); + } + if ($project_id && $result) { + header('Location: projects.php'); + exit(); + } else + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.project_exists')); + $err->add($i18n->get('error.object_exists')); } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.projectForm.project_name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.add_project')); +$smarty->assign('show_files', $showFiles); +$smarty->assign('show_users', count($users) > 0); +$smarty->assign('show_tasks', $show_tasks); +$smarty->assign('title', $i18n->get('title.add_project')); $smarty->assign('content_page_name', 'project_add.tpl'); $smarty->display('index.tpl'); diff --git a/project_delete.php b/project_delete.php index 832bf4f73..4f4353b87 100644 --- a/project_delete.php +++ b/project_delete.php @@ -1,60 +1,42 @@ tracking_mode && MODE_PROJECTS_AND_TASKS != $user->tracking_mode)) { +// Access checks. +if (!ttAccessAllowed('manage_projects')) { header('Location: access_denied.php'); exit(); } - +if (MODE_PROJECTS != $user->getTrackingMode() && MODE_PROJECTS_AND_TASKS != $user->getTrackingMode()) { + header('Location: feature_disabled.php'); + exit(); +} $cl_project_id = (int)$request->getParameter('id'); $project = ttProjectHelper::get($cl_project_id); +if (!$project) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + $project_to_delete = $project['name']; $form = new Form('projectDeleteForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_project_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); if ($request->isPost()) { if ($request->getParameter('btn_delete')) { - if(ttProjectHelper::get($cl_project_id)) { - if (ttProjectHelper::delete($cl_project_id)) { - header('Location: projects.php'); - exit(); - } else - $err->add($i18n->getKey('error.db')); + if (ttProjectHelper::delete($cl_project_id)) { + header('Location: projects.php'); + exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } elseif ($request->getParameter('btn_cancel')) { header('Location: projects.php'); exit(); @@ -64,6 +46,6 @@ $smarty->assign('project_to_delete', $project_to_delete); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.projectDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->getKey('title.delete_project')); +$smarty->assign('title', $i18n->get('title.delete_project')); $smarty->assign('content_page_name', 'project_delete.tpl'); $smarty->display('index.tpl'); diff --git a/project_edit.php b/project_edit.php index 11abccc83..a0190ff83 100644 --- a/project_edit.php +++ b/project_edit.php @@ -1,51 +1,44 @@ tracking_mode && MODE_PROJECTS_AND_TASKS != $user->tracking_mode)) { +// Access checks. +if (!ttAccessAllowed('manage_projects')) { header('Location: access_denied.php'); exit(); } - +if (MODE_PROJECTS != $user->getTrackingMode() && MODE_PROJECTS_AND_TASKS != $user->getTrackingMode()) { + header('Location: feature_disabled.php'); + exit(); +} $cl_project_id = (int)$request->getParameter('id'); +$project = ttProjectHelper::get($cl_project_id); +if (!$project) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. -$users = ttTeamHelper::getActiveUsers(); +// Use custom fields plugin if it is enabled. +if ($user->isPluginEnabled('cf')) { + require_once('plugins/CustomFields.class.php'); + $custom_fields = new CustomFields(); + $smarty->assign('custom_fields', $custom_fields); +} + +$users = ttGroupHelper::getActiveUsers(); foreach ($users as $user_item) $all_users[$user_item['id']] = $user_item['name']; -$tasks = ttTeamHelper::getActiveTasks($user->team_id); +$tasks = ttGroupHelper::getActiveTasks(); foreach ($tasks as $task_item) $all_tasks[$task_item['id']] = $task_item['name']; +$show_tasks = MODE_PROJECTS_AND_TASKS == $user->getTrackingMode() && count($tasks) > 0; if ($request->isPost()) { $cl_name = trim($request->getParameter('project_name')); @@ -53,81 +46,131 @@ $cl_status = $request->getParameter('status'); $cl_users = $request->getParameter('users', array()); $cl_tasks = $request->getParameter('tasks', array()); + // If we have project custom fields - collect input. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $control_name = 'project_field_'.$projectField['id']; + $projectCustomFields[$projectField['id']] = array('field_id' => $projectField['id'], + 'control_name' => $control_name, + 'label' => $projectField['label'], + 'type' => $projectField['type'], + 'required' => $projectField['required'], + 'value' => trim($request->getParameter($control_name))); + } + } } else { - $project = ttProjectHelper::get($cl_project_id); $cl_name = $project['name']; $cl_description = $project['description']; + // If we have project custom fields - collect values from database. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $control_name = 'project_field_'.$projectField['id']; + $projectCustomFields[$projectField['id']] = array('field_id' => $projectField['id'], + 'control_name' => $control_name, + 'label' => $projectField['label'], + 'type' => $projectField['type'], + 'required' => $projectField['required'], + 'value' => $custom_fields->getEntityFieldValue(CustomFields::ENTITY_PROJECT, $cl_project_id, $projectField['id'], $projectField['type'])); + } + } $cl_status = $project['status']; - - $mdb2 = getConnection(); - $sql = "select user_id from tt_user_project_binds where status = 1 and project_id = $cl_project_id"; - $res = $mdb2->query($sql); - if (is_a($res, 'PEAR_Error')) - die($res->getMessage()); - while ($row = $res->fetchRow()) - $cl_users[] = $row['user_id']; - + $cl_users = ttProjectHelper::getAssignedUsers($cl_project_id); $cl_tasks = explode(',', $project['tasks']); } $form = new Form('projectForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_project_id)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'project_name','style'=>'width: 250px;','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 250px; height: 40px;','value'=>$cl_description)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'project_name','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +// If we have custom fields - add controls for them. +if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + $form->addInput(array('type'=>'text','name'=>$field_name,'value'=>$projectCustomFields[$projectField['id']]['value'])); + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + $form->addInput(array('type'=>'combobox','name'=>$field_name, + 'data'=>CustomFields::getOptions($projectField['id']), + 'value'=>$projectCustomFields[$projectField['id']]['value'], + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + } + } +} $form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status, - 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive')))); + 'data'=>array(ACTIVE=>$i18n->get('dropdown.status_active'),INACTIVE=>$i18n->get('dropdown.status_inactive')))); $form->addInput(array('type'=>'checkboxgroup','name'=>'users','data'=>$all_users,'layout'=>'H','value'=>$cl_users)); -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) +if ($show_tasks) $form->addInput(array('type'=>'checkboxgroup','name'=>'tasks','data'=>$all_tasks,'layout'=>'H','value'=>$cl_tasks)); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->getKey('button.copy'))); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); +$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->get('button.copy'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description')); + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + // Validate input in project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($projectCustomFields as $projectField) { + // Validation is the same for text and dropdown fields. + if (!ttValidString($projectField['value'], !$projectField['required'])) $err->add($i18n->get('error.field'), htmlspecialchars($projectField['label'])); + } + } + if (!ttValidStatus($cl_status)) $err->add($i18n->get('error.field'), $i18n->get('label.status')); + if (!ttGroupHelper::validateCheckboxGroupInput($cl_users, 'tt_users')) $err->add($i18n->get('error.field'), $i18n->get('label.users')); + if (!ttGroupHelper::validateCheckboxGroupInput($cl_tasks, 'tt_tasks')) $err->add($i18n->get('error.field'), $i18n->get('label.tasks')); if ($err->no()) { if ($request->getParameter('btn_save')) { $existing_project = ttProjectHelper::getProjectByName($cl_name); if (!$existing_project || ($cl_project_id == $existing_project['id'])) { - // Update project information. - if (ttProjectHelper::update(array( - 'id' => $cl_project_id, - 'name' => $cl_name, - 'description' => $cl_description, - 'status' => $cl_status, - 'users' => $cl_users, - 'tasks' => $cl_tasks))) { - header('Location: projects.php'); - exit(); + // Update project information. + $result = ttProjectHelper::update(array( + 'id' => $cl_project_id, + 'name' => $cl_name, + 'description' => $cl_description, + 'status' => $cl_status, + 'users' => $cl_users, + 'tasks' => $cl_tasks)); + // Update project custom fields if we have them. + if ($result && isset($custom_fields) && $custom_fields->projectFields) { + $result = $custom_fields->updateEntityFields(CustomFields::ENTITY_PROJECT, $cl_project_id, $projectCustomFields); + } + if ($result) { + header('Location: projects.php'); + exit(); + } else + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.project_exists')); + $err->add($i18n->get('error.object_exists')); } if ($request->getParameter('btn_copy')) { if (!ttProjectHelper::getProjectByName($cl_name)) { - if (ttProjectHelper::insert(array( - 'team_id' => $user->team_id, - 'name' => $cl_name, + $project_id = ttProjectHelper::insert(array('name' => $cl_name, 'description' => $cl_description, 'users' => $cl_users, 'tasks' => $cl_tasks, - 'status' => ACTIVE))) { + 'status' => ACTIVE)); + // Insert project custom fields if we have them. + $result = true; + if ($project_id && isset($custom_fields) && $custom_fields->projectFields) { + $result = $custom_fields->insertEntityFields(CustomFields::ENTITY_PROJECT, $project_id, $projectCustomFields); + } + if ($project_id && $result) { header('Location: projects.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.project_exists')); + $err->add($i18n->get('error.object_exists')); } } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.projectForm.name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.edit_project')); +$smarty->assign('onload', 'onLoad="document.projectForm.project_name.focus()"'); +$smarty->assign('show_users', count($users) > 0); +$smarty->assign('show_tasks', $show_tasks); +$smarty->assign('title', $i18n->get('title.edit_project')); $smarty->assign('content_page_name', 'project_edit.tpl'); $smarty->display('index.tpl'); diff --git a/project_files.php b/project_files.php new file mode 100644 index 000000000..8b38a8ce6 --- /dev/null +++ b/project_files.php @@ -0,0 +1,66 @@ +getTrackingMode() && MODE_PROJECTS_AND_TASKS != $user->getTrackingMode()) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_project_id = (int)$request->getParameter('id'); +$project = ttProjectHelper::get($cl_project_id); +if (!$project) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$cl_description = ''; +if ($request->isPost()) { + $cl_description = trim($request->getParameter('description')); +} + +$fileHelper = new ttFileHelper($err); +$files = $fileHelper::getEntityFiles($cl_project_id, 'project'); + +$form = new Form('fileUploadForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_project_id)); +$form->addInput(array('type'=>'upload','name'=>'newfile','value'=>$i18n->get('button.submit'))); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.add'))); + +if ($request->isPost()) { + // We are adding a new file. + + // Validate user input. + if (!$_FILES['newfile']['name']) $err->add($i18n->get('error.upload')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + // Finished validating user input. + + if ($err->no()) { + $fields = array('entity_type'=>'project', + 'entity_id' => $cl_project_id, + 'file_name' => $_FILES['newfile']['name'], + 'description'=>$cl_description); + if ($fileHelper->putFile($fields)) { + header('Location: project_files.php?id='.$cl_project_id); + exit(); + } + } +} // isPost + +$smarty->assign('can_edit', $user->can('manage_projects')); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('files', $files); +$smarty->assign('title', $i18n->get('title.project_files').': '.$project['name']); +$smarty->assign('content_page_name', 'entity_files.tpl'); +$smarty->display('index.tpl'); diff --git a/projects.php b/projects.php index d9f368512..ed4d371df 100644 --- a/projects.php +++ b/projects.php @@ -1,49 +1,34 @@ tracking_mode && MODE_PROJECTS_AND_TASKS != $user->tracking_mode)) { +// Access checks. +if (!(ttAccessAllowed('view_own_projects') || ttAccessAllowed('manage_projects'))) { header('Location: access_denied.php'); exit(); } +if (MODE_PROJECTS != $user->getTrackingMode() && MODE_PROJECTS_AND_TASKS != $user->getTrackingMode()) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. -if($user->canManageTeam()) { - $active_projects = ttTeamHelper::getActiveProjects($user->team_id); - $inactive_projects = ttTeamHelper::getInactiveProjects($user->team_id); -} else - $active_projects = $user->getAssignedProjects(); +$showFiles = $user->isPluginEnabled('at'); + +if($user->can('manage_projects')) { + $active_projects = ttGroupHelper::getActiveProjects($showFiles); + $inactive_projects = ttGroupHelper::getInactiveProjects($showFiles); +} else { + $options['include_files'] = $showFiles; + $active_projects = $user->getAssignedProjects($options); +} $smarty->assign('active_projects', $active_projects); $smarty->assign('inactive_projects', $inactive_projects); -$smarty->assign('title', $i18n->getKey('title.projects')); +$smarty->assign('show_files', $showFiles); +$smarty->assign('title', $i18n->get('title.projects')); $smarty->assign('content_page_name', 'projects.tpl'); $smarty->display('index.tpl'); diff --git a/puncher.php b/puncher.php new file mode 100644 index 000000000..26b371fcd --- /dev/null +++ b/puncher.php @@ -0,0 +1,339 @@ +isPluginEnabled('pu')) { + header('Location: feature_disabled.php'); + exit(); +} +// If we are passed in browser_today, make sure it is in correct format. +$browser_today = null; // Reused below beyond access checks. +if ($request->isPost()) { + // Validate that browser_today parameter is in correct format. + $browser_today = $request->getParameter('browser_today'); + if ($browser_today && !ttValidDbDateFormatDate($browser_today)) { + header('Location: access_denied.php'); + exit(); + } +} +// If we are passed in browser_time, make sure it is in correct format. +$browser_time = null; // Reused below beyond access checks. +if ($request->isPost()) { + // Validate that browser_time parameter is in correct format. + $browser_time = $request->getParameter('browser_time'); + if ($browser_time && !ttValidTime($browser_time)) { + header('Location: access_denied.php'); + exit(); + } +} +// End of access checks. + +$showClient = $user->isPluginEnabled('cl'); +$showBillable = $user->isPluginEnabled('iv'); +$trackingMode = $user->getTrackingMode(); +$showProject = MODE_PROJECTS == $trackingMode || MODE_PROJECTS_AND_TASKS == $trackingMode; +$showTask = MODE_PROJECTS_AND_TASKS == $trackingMode; +$taskRequired = false; +if ($showTask) $taskRequired = $user->getConfigOption('task_required'); +$oneUncompleted = $user->getConfigOption('one_uncompleted'); + +// Initialize $cl_date. +$date_today = new ttDate($browser_today); // Initialize to browser today if we are passed it in, otherwise server today. +$cl_date = $date_today->toString(); + +// Use custom fields plugin if it is enabled. +if ($user->isPluginEnabled('cf')) { + require_once('plugins/CustomFields.class.php'); + $custom_fields = new CustomFields(); + $smarty->assign('custom_fields', $custom_fields); +} + +// Obtain first uncompleted record for today. If there are multiples, we operate with first found. +$uncompletedToday = ttTimeHelper::getFirstUncompletedForDate($user->getUser(), $date_today->toString()); +$enable_controls = ($uncompletedToday == null); + +// Obtain first found uncompleted record, not necessarily for today. Used to prohibit entry in "One uncompleted" mode. +$uncompleted = ttTimeHelper::getUncompleted($user->getUser()); + +// Initialize variables. +$cl_start = $browser_time; +$cl_finish = $browser_time; +$cl_duration = $cl_note = null; +// Disabled controls are not posted. Therefore, && $enable_controls condition in several places below. +// This allows us to get values from session when controls are disabled and reset to null when not. +$cl_billable = 1; +if ($user->isPluginEnabled('iv')) { + $cl_billable = $request->getParameter('billable', ($request->isPost() && $enable_controls ? null : @$_SESSION['billable'])); + $_SESSION['billable'] = $cl_billable; +} +$cl_client = $request->getParameter('client', ($request->isPost() && $enable_controls ? null : @$_SESSION['client'])); +$_SESSION['client'] = $cl_client; +$cl_project = $request->getParameter('project', ($request->isPost() && $enable_controls ? null : @$_SESSION['project'])); +$_SESSION['project'] = $cl_project; +$cl_task = $request->getParameter('task', ($request->isPost() && $enable_controls ? null : @$_SESSION['task'])); +$_SESSION['task'] = $cl_task; + +// Handle time custom fields. +$timeCustomFields = array(); +if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $control_name = 'time_field_'.$timeField['id']; + $cl_control_name = $request->getParameter($control_name, ($request->isPost() && $enable_controls ? null : @$_SESSION[$control_name])); + $_SESSION[$control_name] = $cl_control_name; + $timeCustomFields[$timeField['id']] = array('field_id' => $timeField['id'], + 'control_name' => $control_name, + 'label' => $timeField['label'], + 'type' => $timeField['type'], + 'required' => $timeField['required'], + 'value' => trim($cl_control_name)); + } +} + +// Elements of timeRecordForm. +$form = new Form('timeRecordForm'); + +// Dropdown for clients in MODE_TIME. Use all active clients. +if (MODE_TIME == $trackingMode && $showClient) { + $active_clients = ttGroupHelper::getActiveClients(true); + $form->addInput(array('type'=>'combobox', + 'onchange'=>'fillProjectDropdown(this.value);', + 'name'=>'client', + 'enable'=>$enable_controls, + 'value'=>$cl_client, + 'data'=>$active_clients, + 'datakeys'=>array('id', 'name'), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + // Note: in other modes the client list is filtered to relevant clients only. See below. +} + +// Billable checkbox. +if ($showBillable) { + $form->addInput(array('type'=>'checkbox','name'=>'billable','value'=>$cl_billable,'enable'=>$enable_controls)); +} + +// If we have time custom fields - add controls for them. +if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + if ($timeField['type'] == CustomFields::TYPE_TEXT) { + $form->addInput(array('type'=>'text', + 'name'=>$field_name, + 'enable'=>$enable_controls, + 'value'=>$timeCustomFields[$timeField['id']]['value'])); + } elseif ($timeField['type'] == CustomFields::TYPE_DROPDOWN) { + $form->addInput(array('type'=>'combobox','name'=>$field_name, + 'data'=>CustomFields::getOptions($timeField['id']), + 'value'=>$timeCustomFields[$timeField['id']]['value'], + 'enable'=>$enable_controls, + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + } + } +} + +// If we show project dropdown, add controls for project and client. +$project_list = $client_list = array(); +if ($showProject) { + // Dropdown for projects assigned to user. + $project_list = $user->getAssignedProjects(); + $form->addInput(array('type'=>'combobox', + 'onchange'=>'fillTaskDropdown(this.value);', + 'name'=>'project', + 'enable'=>$enable_controls, + 'value'=>$cl_project, + 'data'=>$project_list, + 'datakeys'=>array('id','name'), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + + // Client dropdown. + if ($showClient) { + $active_clients = ttGroupHelper::getActiveClients(true); + // We need an array of assigned project ids to do some trimming. + foreach($project_list as $project) + $projects_assigned_to_user[] = $project['id']; + + // Build a client list out of active clients. Use only clients that are relevant to user. + // Also trim their associated project list to only assigned projects (to user). + foreach($active_clients as $client) { + $projects_assigned_to_client = explode(',', $client['projects']); + $intersection = array_intersect($projects_assigned_to_client, $projects_assigned_to_user); + if ($intersection) { + $client['projects'] = implode(',', $intersection); + $client_list[] = $client; + } + } + $form->addInput(array('type'=>'combobox', + 'onchange'=>'fillProjectDropdown(this.value);', + 'name'=>'client', + 'enable'=>$enable_controls, + 'value'=>$cl_client, + 'data'=>$client_list, + 'datakeys'=>array('id', 'name'), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + } +} + +// Task dropdown. +$task_list = array(); +if ($showTask) { + $task_list = ttGroupHelper::getActiveTasks(); + $form->addInput(array('type'=>'combobox', + 'name'=>'task', + 'enable'=>$enable_controls, + 'value'=>$cl_task, + 'data'=>$task_list, + 'datakeys'=>array('id','name'), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); +} + +// A hidden control for today's date from user's browser. +$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on button click. + +// A hidden control for current time from user's browser. +$form->addInput(array('type'=>'hidden','name'=>'browser_time','value'=>'')); // User current time, which gets filled in on button click. + +// Start and stop buttons. +$enable_start = $uncompletedToday ? false : true; +if (!$uncompletedToday) + $form->addInput(array('type'=>'submit','name'=>'btn_start','onclick'=>'browser_today.value=get_date();browser_time.value=get_time()','value'=>$i18n->get('button.start'),'enable'=>$enable_start)); +else + $form->addInput(array('type'=>'submit','name'=>'btn_stop','onclick'=>'browser_today.value=get_date();browser_time.value=get_time()','value'=>$i18n->get('button.stop'),'enable'=>!$enable_start)); + +// Submit. +if ($request->isPost()) { + if ($request->getParameter('btn_start')) { + // Start button clicked. We need to create a new uncompleted record with only the start time. + $cl_finish = null; + + // Validate user input. + if ($showClient && $user->isOptionEnabled('client_required') && !$cl_client) + $err->add($i18n->get('error.client')); + // Validate input in time custom fields. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($timeCustomFields as $timeField) { + // Validation is the same for text and dropdown fields. + if (!ttValidString($timeField['value'], !$timeField['required'])) $err->add($i18n->get('error.field'), htmlspecialchars($timeField['label'])); + } + } + if ($showProject) { + if (!$cl_project) $err->add($i18n->get('error.project')); + } + if ($showTask && $taskRequired) { + if (!$cl_task) $err->add($i18n->get('error.task')); + } + // Finished validating user input. + + // Prohibit creating entries in future. + if (!$user->isOptionEnabled('future_entries')) { + // $date_today is already browser_today, just check if the date we are using is after server tomorrow. + $server_tomorrow = new ttDate(); + $server_tomorrow->incrementDay(); + if ($date_today->after($server_tomorrow)) + $err->add($i18n->get('error.future_date')); + } + + // Prohibit creating time entries in locked interval. + if ($user->isDateLocked($date_today)) + $err->add($i18n->get('error.range_locked')); + + // Prohibit creating another uncompleted record. + if ($err->no() && $uncompleted && $oneUncompleted) { + $err->add($i18n->get('error.uncompleted_exists')." ".$i18n->get('error.goto_uncompleted').""); + } + + // Prohibit creating an overlapping record. + if ($err->no()) { + if (ttTimeHelper::overlaps($user->getUser(), $cl_date, $cl_start, $cl_finish)) + $err->add($i18n->get('error.overlap')); + } + + if ($err->no()) { + $id = ttTimeHelper::insert(array( + 'date' => $cl_date, + 'client' => $cl_client, + 'project' => $cl_project, + 'task' => $cl_task, + 'start' => $cl_start, + 'finish' => $cl_finish, + 'duration' => $cl_duration, + 'note' => $cl_note, + 'billable' => $cl_billable)); + + // Insert time custom fields if we have them. + $result = true; + if ($id && isset($custom_fields) && $custom_fields->timeFields) { + $result = $custom_fields->insertTimeFields($id, $timeCustomFields); + } + + if ($id && $result) { + header('Location: puncher.php'); + exit(); + } + $err->add($i18n->get('error.db')); + } + } + if ($request->getParameter('btn_stop')) { + // Stop button clicked. We need to finish an uncompleted record in progress. + $record = ttTimeHelper::getRecord($uncompletedToday['id']); + + // Can we complete this record? + if (ttTimeHelper::isValidInterval($record['start'], $cl_finish) // finish time is greater than start time + && !ttTimeHelper::overlaps($user->getUser(), $cl_date, $record['start'], $cl_finish)) { // no overlap + $res = ttTimeHelper::update(array( + 'id'=>$record['id'], + 'date'=>$cl_date, + 'client'=>$record['client_id'], + 'project'=>$record['project_id'], + 'task'=>$record['task_id'], + 'start'=>$record['start'], + 'finish'=>$cl_finish, + 'note'=>$record['comment'], + 'billable'=>$record['billable'])); + if ($res) { + header('Location: puncher.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } else { + // Cannot complete, redirect for manual edit. + header('Location: time_edit.php?id='.$record['id']); + exit(); + } + } +} // isPost + +$week_total = ttTimeHelper::getTimeForWeek($date_today); +$timeRecords = ttTimeHelper::getRecords($cl_date); + +$smarty->assign('week_total', $week_total); +$smarty->assign('uncompleted_today', $uncompletedToday); +$smarty->assign('show_client', $showClient); +$smarty->assign('show_billable', $showBillable); +$smarty->assign('show_project', $showProject); +$smarty->assign('show_task', $showTask); +$smarty->assign('task_required', $taskRequired); +$smarty->assign('day_total', ttTimeHelper::getTimeForDay($cl_date)); +$smarty->assign('time_records', $timeRecords); +$smarty->assign('show_record_custom_fields', $user->isOptionEnabled('record_custom_fields')); +$smarty->assign('show_start', true); +$smarty->assign('client_list', $client_list); +$smarty->assign('project_list', $project_list); +$smarty->assign('task_list', $task_list); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="fillDropdowns()"'); +$smarty->assign('timestring', $date_today->toString($user->getDateFormat())); +$smarty->assign('title', $i18n->get('title.puncher')); +$smarty->assign('content_page_name', 'puncher.tpl'); +$smarty->display('index.tpl'); diff --git a/puncher_conf.php b/puncher_conf.php new file mode 100644 index 000000000..51610f693 --- /dev/null +++ b/puncher_conf.php @@ -0,0 +1,37 @@ +getConfigHelper(); + +if ($request->isPost()) { + $cl_puncher_menu = (bool)$request->getParameter('puncher_menu'); +} else { + $cl_puncher_menu = $config->getDefinedValue('puncher_menu'); +} + +$form = new Form('puncherConfigForm'); +$form->addInput(array('type'=>'checkbox','name'=>'puncher_menu','value'=>$cl_puncher_menu)); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); + +if ($request->isPost()){ + // Update config. + $config->setDefinedValue('puncher_menu', $cl_puncher_menu); + if (!$user->updateGroup(array('config' => $config->getConfig()))) { + $err->add($i18n->get('error.db')); + } +} + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.puncher')); +$smarty->assign('content_page_name', 'puncher_conf.tpl'); +$smarty->display('index.tpl'); diff --git a/quotas.php b/quotas.php index d846ae25d..55214093f 100644 --- a/quotas.php +++ b/quotas.php @@ -1,41 +1,21 @@ isPluginEnabled('mq')) { +// Access checks. +if (!ttAccessAllowed('manage_advanced_settings')) { header('Location: access_denied.php'); exit(); } +if (!$user->isPluginEnabled('mq')) { + header('Location: feature_disabled.php'); + exit(); +} // Start and end fallback values for the Year dropdown. $yearStart = 2015; @@ -60,7 +40,7 @@ if (!$selectedYear or !ttValidInteger($selectedYear)){ $selectedYear = date('Y'); } else { - $selectedYear = intval($selectedYear); + $selectedYear = (int) $selectedYear; } // Months are zero indexed. @@ -69,47 +49,63 @@ $quota = new MonthlyQuota(); if ($request->isPost()){ - // TODO: Add parameter validation. - $res = false; - if ($_POST['btn_hours']){ + // Validate user input. + $workdayMinutes = ttTimeHelper::postedDurationToMinutes($request->getParameter('workdayHours')); + if (false === $workdayMinutes || $workdayMinutes <= 0 ) + $err->add($i18n->get('error.field'), $i18n->get('form.quota.workday_hours')); - // User changed workday hours for team. - $hours = (int)$request->getParameter('workdayHours'); - $res = ttTeamHelper::update($user->team_id, array('name'=>$user->team,'workday_hours'=>$hours)); + for ($i = 0; $i < count($months); $i++){ + $val = $request->getParameter($months[$i]); + $monthMinutes = ttTimeHelper::postedDurationToMinutes($val, 44640/*24*60*31*/); + if (false === $monthMinutes || $monthMinutes < 0) + $err->add($i18n->get('error.field'), $months[$i]); } - if ($_POST['btn_submit']){ - // User pressed the Save button under monthly quotas table. - $postedYear = $request->getParameter('year'); - $selectedYear = intval($postedYear); + // Finished validating user input. + + if ($err->no()) { + + // Handle workday hours. + $workday_minutes = ttTimeHelper::postedDurationToMinutes($request->getParameter('workdayHours')); + if ($workday_minutes != $user->getWorkdayMinutes()) { + if (!$user->updateGroup(array('workday_minutes'=>$workday_minutes))) + $err->add($i18n->get('error.db')); + } + + // Handle monthly quotas for a selected year. + $selectedYear = (int) $request->getParameter('year'); for ($i = 0; $i < count($months); $i++){ - $res = $quota->update($postedYear, $i+1, $request->getParameter($months[$i])); + $quota_in_minutes = ttTimeHelper::postedDurationToMinutes($request->getParameter($months[$i]), 44640/*24*60*31*/); + if (!$quota->update($selectedYear, $i+1, $quota_in_minutes)) + $err->add($i18n->get('error.db')); + } + + if ($err->no()) { + // Redisplay the form. + header('Location: quotas.php?year='.$selectedYear); + exit(); } - } - if ($res) { - header('Location: profile_edit.php'); - exit(); - } else { - $err->add($i18n->getKey('error.db')); } } -// Returns monthly quotas where January is month 1, not 0. +// Get monthly quotas for the entire year. $monthsData = $quota->get($selectedYear); +$workdayHours = ttTimeHelper::toAbsDuration($user->getWorkdayMinutes(), true); $form = new Form('monthlyQuotasForm'); -$form->addInput(array('type'=>'text', 'name'=>'workdayHours', 'value'=>$user->workday_hours, 'style'=>'width:50px')); -$form->addInput(array('type'=>'combobox','name'=>'year','data'=>$years,'datakeys'=>array('id','name'),'value'=>$selectedYear,'onchange'=>'yearChange(this.value);')); +$form->addInput(array('type'=>'text','class'=>'quota-field','name'=>'workdayHours','value'=>$workdayHours)); +$form->addInput(array('type'=>'combobox','class'=>'dropdown-field-short','name'=>'year','data'=>$years,'datakeys'=>array('id','name'),'value'=>$selectedYear,'onchange'=>'yearChange(this.value);')); for ($i=0; $i < count($months); $i++) { $value = ""; if (array_key_exists($i+1, $monthsData)){ $value = $monthsData[$i+1]; + $value = ttTimeHelper::toAbsDuration($value, true); } $name = $months[$i]; - $form->addInput(array('type'=>'text','name'=>$name,'maxlength'=>3,'value'=> $value,'style'=>'width:50px')); + $form->addInput(array('type'=>'text','class'=>'quota-field','name'=>$name,'maxlength'=>6,'value'=> $value)); } $smarty->assign('months', $months); $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.monthly_quotas')); +$smarty->assign('title', $i18n->get('title.monthly_quotas')); $smarty->assign('content_page_name', 'quotas.tpl'); $smarty->display('index.tpl'); diff --git a/readme.txt b/readme.txt index 437d4c3f7..90cdc970d 100644 --- a/readme.txt +++ b/readme.txt @@ -1,53 +1,55 @@ -Anuko Time Tracker. -Copyright (c) Anuko (https://www.anuko.com). +Anuko Time Tracker +Copyright (c) Anuko (https://www.anuko.com) -Project home page: https://www.anuko.com/time_tracker/index.htm -Free hosting of Time Tracker for individuals and small teams is available at https://timetracker.anuko.com +Project home page: https://www.anuko.com/time-tracker/index.htm +Forum: https://www.anuko.com/forum/viewforum.php?f=4 +Info for developers: https://www.anuko.com/time-tracker/info-for-developers.htm +Free hosting of Time Tracker for individuals and small groups is available at https://timetracker.anuko.com -Each file in this archive is protected by the LIBERAL FREEWARE LICENSE. +Unless otherwise noted, files in this archive are protected by the LIBERAL FREEWARE LICENSE. Read the file license.txt for details. INSTALLATION INSTRUCTIONS -Detailed documentation is available at https://www.anuko.com/time_tracker/install_guide/index.htm +Documentation is available at https://www.anuko.com/time-tracker/install-guide/index.htm The general installation procedure looks like this: - Install a web server and make sure it can serve HTML documents. -- Install PHP, configure your server to work with PHP scripts, and make sure it can work with PHP files. -- Install the following PHP extensions: MySQL and GD. The GD extension is needed for pie-charts only. -- Install a database server such as MySQL and make sure it is working properly. -- Install, configure, and test Anuko Time Tracker like so: - -1) Unpack distribution files into a selected directory for Apache web server. -2) Create a database using the mysql.sql file in the distribution. -3) If you are upgrading from earlier versions run dbinstall.php from your browser and do the required "Update database structure" steps. -4) Create user name and password to access the time tracker database. -5) Change $dsn value in /WEB-INF/config.php file to reflect your database connection parameters (user name and password). -6) For UNIX systems set full access rights for catalog WEB-INF/templates_c/ (chmod 777 templates_c). -7) If you install time tracker into a sub-directory of your site reflect this in the APP_NAME parameter in /WEB-INF/config.php file. For example, for http://localhost/timetracker/ set APP_NAME = "timetracker". -8) Login to your time tracker site as admin with password "secret" without quotes and create at least one team. -9) Change admin password (on the admin "options" page). You can also use the following SQL console command: +- Install PHP, configure your server to work with PHP scripts, and make sure it can work with PHP files. +- Install the following PHP extensions: mbstring and GD. The GD extension is needed for pie-charts only. +- Install a database server such as MySQL and make sure it is working properly. +- Install, configure, and test Anuko Time Tracker like so: + +1) Unpack distribution files into a selected directory for your web server. +2) Allow writing to WEB-INF/templates_c/. Some hosting providers like Hostmonster additionally require files having 644 and directories 755. Otherwise you'll see error 500 (bad permissions, writable by group). +3) Create a database using the mysql.sql file in the distribution. +4) Create user name and password to access the time tracker database. +5) Create a config file by coping WEB-INF/config.php.dist to WEB-INF/config.php. +6) Change $dsn value in /WEB-INF/config.php file to reflect your database connection parameters (user name and password). +7) If you are upgrading from earlier Time Tracker version run dbinstall.php from your browser and do only the required "Update database structure" steps. +8) If you install time tracker into a sub-directory of your site reflect this in the DIR_NAME parameter in /WEB-INF/config.php file. For example, for http://localhost/timetracker/ set DIR_NAME = "timetracker". +9) Login to your time tracker site as admin with password "secret" without quotes and create at least one group. +10) Change admin password (on the admin "options" page). You can also use the following SQL console command: update tt_users set password = md5('new_password_here') where login='admin' or by using the "Change password of administrator account" option in http://your_time_tracker_site/dbinstall.php -10) Test if everything is working. -11) Remove dbinstall.php file from your installation directory. +11) Test if everything is working. +12) Remove dbinstall.php file from your installation directory. UPGRADE FROM EARLIER VERSIONS -See https://www.anuko.com/time_tracker/upgrade.htm +See https://www.anuko.com/time-tracker/install-guide/upgrade.htm BLANK PAGES IN TIME TRACKER If you see a blank page in when trying to access Anuko Time Tracker it may mean many things, among others, such as: - * MySQL extension for PHP not installed or not working. * Time tracker database not created. * Access (login / password) to the database is not configured properly in config.php. - * MySQL service is down. + * MySQL service is down. * On UNIX systems - no full access rights for catalog WEB-INF/templates_c/ (chmod 777 templates_c). You need to thoroughly test each and every component to make sure they work together nicely. @@ -60,4 +62,4 @@ Support is available on per-incident basis - see https://www.anuko.com/support.h CHANGE LOG -Change log is available at https://www.anuko.com/time_tracker/change_log/index.htm \ No newline at end of file +Change log is available at https://www.anuko.com/time-tracker/change-log/index.htm \ No newline at end of file diff --git a/register.php b/register.php index 9984460cc..6f54ccf5b 100644 --- a/register.php +++ b/register.php @@ -1,109 +1,102 @@ isPasswordExternal()) { +if (!isTrue('MULTIORG_MODE') || $auth->isPasswordExternal()) { header('Location: login.php'); exit(); } +// Use the "limit" plugin if we have one. Ignore include errors. +// The "limit" plugin is not required for normal operation of Time Tracker. +@include('plugins/limit/register.php'); + $auth->doLogout(); if (!defined('CURRENCY_DEFAULT')) define('CURRENCY_DEFAULT', '$'); +$cl_group_name = $cl_currency = $cl_lang = $cl_manager_name = $cl_manager_login = +$cl_password1 = $cl_password2 = $cl_manager_email = ''; if ($request->isPost()) { - $cl_team_name = trim($request->getParameter('team_name')); + $cl_group_name = trim($request->getParameter('group_name')); $cl_currency = trim($request->getParameter('currency')); if (!$cl_currency) $cl_currency = CURRENCY_DEFAULT; + $cl_lang = $request->getParameter('lang'); $cl_manager_name = trim($request->getParameter('manager_name')); $cl_manager_login = trim($request->getParameter('manager_login')); $cl_password1 = $request->getParameter('password1'); $cl_password2 = $request->getParameter('password2'); $cl_manager_email = trim($request->getParameter('manager_email')); -} else +} else { $cl_currency = CURRENCY_DEFAULT; + $cl_lang = $i18n->lang; // Browser setting from initialize.php. +} + +$form = new Form('groupForm'); +$form->addInput(array('type'=>'text','name'=>'group_name','value'=>$cl_group_name)); +$form->addInput(array('type'=>'text','name'=>'currency','value'=>$cl_currency)); + +// Prepare an array of available languages. +$lang_files = I18n::getLangFileList(); +foreach ($lang_files as $lfile) { + $content = file(RESOURCE_DIR."/".$lfile); + $lname = ''; + foreach ($content as $line) { + if (strstr($line, 'i18n_language')) { + $a = explode('=', $line); + $lname = trim(str_replace(';','',str_replace("'","",$a[1]))); + break; + } + } + unset($content); + $longname_lang[] = array('id'=>I18n::getLangFromFilename($lfile),'name'=>$lname); +} +$longname_lang = mu_sort($longname_lang, 'name'); +$form->addInput(array('type'=>'combobox','name'=>'lang','data'=>$longname_lang,'datakeys'=>array('id','name'),'value'=>$cl_lang)); -$form = new Form('profileForm'); -$form->addInput(array('type'=>'text','maxlength'=>'200','name'=>'team_name','value'=>$cl_team_name)); -$form->addInput(array('type'=>'text','maxlength'=>'7','name'=>'currency','value'=>$cl_currency)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_name','value'=>$cl_manager_name)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_login','value'=>$cl_manager_login)); -$form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password1','aspassword'=>true,'value'=>$cl_password1)); -$form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'password2','aspassword'=>true,'value'=>$cl_password2)); +$form->addInput(array('type'=>'text','name'=>'manager_name','value'=>$cl_manager_name)); +$form->addInput(array('type'=>'text','name'=>'manager_login','value'=>$cl_manager_login)); +$form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'password1','value'=>$cl_password1)); +$form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'password2','value'=>$cl_password2)); $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'manager_email','value'=>$cl_manager_email)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.submit'))); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.submit'))); if ($request->isPost()) { - // Validate user input. - if (!ttValidString($cl_team_name, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.team_name')); - if (!ttValidString($cl_currency, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.currency')); - if (!ttValidString($cl_manager_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_name')); - if (!ttValidString($cl_manager_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_login')); - if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); - if ($cl_password1 !== $cl_password2) - $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - if (!ttValidEmail($cl_manager_email, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); + // Note: user input validation is done in ttRegistrator constructor. + + // Create fields array for ttRegistrator instance. + $fields = array( + 'user_name' => $cl_manager_name, + 'login' => $cl_manager_login, + 'password1' => $cl_password1, + 'password2' => $cl_password2, + 'email' => $cl_manager_email, + 'group_name' => $cl_group_name, + 'currency' => $cl_currency, + 'lang' => $cl_lang); + + // Create an instance of ttRegistrator class. + import('ttRegistrator'); + $registrator = new ttRegistrator($fields, $err); + $registrator->register(); if ($err->no()) { - if (!ttUserHelper::getUserByLogin($cl_manager_login)) { - // Create a new team. - $team_id = ttTeamHelper::insert(array('name'=>$cl_team_name,'currency'=>$cl_currency)); - if ($team_id) { - // Team created, now create a team manager. - $user_id = ttUserHelper::insert(array( - 'team_id' => $team_id, - 'role' => ROLE_MANAGER, - 'name' => $cl_manager_name, - 'login' => $cl_manager_login, - 'password' => $cl_password1, - 'email' => $cl_manager_email)); - } - if ($team_id && $user_id) { - if ($auth->doLogin($cl_manager_login, $cl_password1)) { - setcookie('tt_login', $cl_manager_login, time() + COOKIE_EXPIRE, '/'); - header('Location: time.php'); - } else { - header('Location: login.php'); - } - exit(); - } else - $err->add($i18n->getKey('error.db')); - } else - $err->add($i18n->getKey('error.user_exists')); + // Registration successful. + if ($auth->doLogin($cl_manager_login, $cl_password1)) { + setcookie(LOGIN_COOKIE_NAME, $cl_manager_login, time() + COOKIE_EXPIRE, '/'); + header('Location: time.php'); + } else { + header('Location: login.php'); + } + exit(); } } // isPost -$smarty->assign('title', $i18n->getKey('title.create_team')); +$smarty->assign('title', $i18n->get('title.add_group')); $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.profileForm.team.focus()"'); +$smarty->assign('onload', 'onLoad="document.groupForm.group_name.focus()"'); $smarty->assign('content_page_name', 'register.tpl'); $smarty->display('index.tpl'); diff --git a/report.php b/report.php index a18ff1f1a..b8a549b69 100644 --- a/report.php +++ b/report.php @@ -1,122 +1,330 @@ getConfig()); +$show_cost_per_hour = $config->getDefinedValue('report_cost_per_hour') && ($user->can('manage_invoices') || $user->isClient()); + +if ($user->isPluginEnabled('ap')) { + $cl_mark_approved_select_option = $request->getParameter('mark_approved_select_options', ($request->isPost() ? null : @$_SESSION['mark_approved_select_option'])); + $_SESSION['mark_approved_select_option'] = $cl_mark_approved_select_option; + $cl_mark_approved_action_option = $request->getParameter('mark_approved_action_options', ($request->isPost() ? null : @$_SESSION['mark_approved_action_option'])); + $_SESSION['mark_aproved_action_option'] = $cl_mark_approved_action_option; +} +if ($user->isPluginEnabled('ps')) { + $cl_mark_paid_select_option = $request->getParameter('mark_paid_select_options', ($request->isPost() ? null : @$_SESSION['mark_paid_select_option'])); + $_SESSION['mark_paid_select_option'] = $cl_mark_paid_select_option; + $cl_mark_paid_action_option = $request->getParameter('mark_paid_action_options', ($request->isPost() ? null : @$_SESSION['mark_paid_action_option'])); + $_SESSION['mark_paid_action_option'] = $cl_mark_paid_action_option; +} +if ($user->isPluginEnabled('iv')) { + $cl_assign_invoice_select_option = $request->getParameter('assign_invoice_select_options', ($request->isPost() ? null : @$_SESSION['assign_invoice_select_option'])); + $_SESSION['assign_invoice_select_option'] = $cl_assign_invoice_select_option; + $cl_recent_invoice_option = $request->getParameter('recent_invoice', ($request->isPost() ? null : @$_SESSION['recent_invoice_option'])); + $_SESSION['recent_invoice_option'] = $cl_recent_invoice_option; +} +if ($user->isPluginEnabled('ts')) { + $cl_assign_timesheet_select_option = $request->getParameter('assign_timesheet_select_options', ($request->isPost() ? null : @$_SESSION['assign_timesheet_select_option'])); + $_SESSION['assign_timesheet_select_option'] = $cl_assign_timesheet_select_option; + $cl_timesheet_option = $request->getParameter('timesheet', ($request->isPost() ? null : @$_SESSION['timesheet_option'])); + $_SESSION['timesheet_option'] = $cl_timesheet_option; +} // Use custom fields plugin if it is enabled. if ($user->isPluginEnabled('cf')) { require_once('plugins/CustomFields.class.php'); - $custom_fields = new CustomFields($user->team_id); + $custom_fields = new CustomFields(); $smarty->assign('custom_fields', $custom_fields); } -$form = new Form('reportForm'); +$form = new Form('reportViewForm'); // Report settings are stored in session bean before we get here from reports.php. -$bean = new ActionForm('reportBean', $form, $request); -$client_id = $bean->getAttribute('client'); -if ($client_id && $bean->getAttribute('chinvoice') && ('no_grouping' == $bean->getAttribute('group_by')) && !$user->isClient()) { +$bean = new ActionForm('reportBean', new Form('reportForm'), $request); +// If we are in post, load the bean from session, as the constructor does it only in get. +if ($request->isPost()) $bean->loadBean(); + +$client_id = (int)$bean->getAttribute('client'); +$options = ttReportHelper::getReportOptions($bean); + +// Do we need to show checkboxes? We show them in the following 4 situations: +// - We can approve items. +// - We can mark items as paid. +// - We can assign items to invoices. +// - We can assign items to a timesheet. +// Determine these conditions separately. +$useMarkApproved = $useMarkPaid = $useAssignToInvoice = $useAssignToTimesheet = false; +if (!$bean->getAttribute('chtotalsonly') && $bean->getAttribute('chapproved') && ($user->can('approve_reports') || $user->can('approve_all_reports'))) + $useMarkApproved = true; +if (!$bean->getAttribute('chtotalsonly') && $bean->getAttribute('chpaid') && $user->can('manage_invoices')) + $useMarkPaid = true; +if (!$bean->getAttribute('chtotalsonly') && $bean->getAttribute('chinvoice') && $client_id && !$user->isClient() && $user->can('manage_invoices')) + $useAssignToInvoice = true; +if (!$bean->getAttribute('chtotalsonly') && $bean->getAttribute('chtimesheet')) { + $timesheets = ttTimesheetHelper::getMatchingTimesheets($options); + if ($timesheets) $useAssignToTimesheet = true; +} + +$use_checkboxes = $useMarkApproved || $useMarkPaid || $useAssignToInvoice || $useAssignToTimesheet; +$smarty->assign('use_checkboxes', $use_checkboxes); + +// Controls for "Mark approved" block. +$smarty->assign('use_mark_approved', false); +if ($useMarkApproved) { + $mark_approved_select_options = array('1'=>$i18n->get('dropdown.all'),'2'=>$i18n->get('dropdown.select')); + $form->addInput(array('type'=>'combobox', + 'name'=>'mark_approved_select_options', + 'class'=>'dropdown-field-with_button', + 'data'=>$mark_approved_select_options, + 'value'=>$cl_mark_approved_select_option)); + $mark_approved_action_options = array('1'=>$i18n->get('dropdown.approved'),'2'=>$i18n->get('dropdown.not_approved')); + $form->addInput(array('type'=>'combobox', + 'name'=>'mark_approved_action_options', + 'class'=>'dropdown-field-with-button', + 'data'=>$mark_approved_action_options, + 'value'=>$cl_mark_approved_action_option)); + $form->addInput(array('type'=>'submit','name'=>'btn_mark_approved','value'=>$i18n->get('button.submit'))); + $smarty->assign('use_mark_approved', true); +} + +// Controls for "Mark paid" block. +$smarty->assign('use_mark_paid', false); +if ($useMarkPaid) { + $mark_paid_select_options = array('1'=>$i18n->get('dropdown.all'),'2'=>$i18n->get('dropdown.select')); + $form->addInput(array('type'=>'combobox', + 'name'=>'mark_paid_select_options', + 'class'=>'dropdown-field-with-button', + 'data'=>$mark_paid_select_options, + 'value'=>$cl_mark_paid_select_option)); + $mark_paid_action_options = array('1'=>$i18n->get('dropdown.paid'),'2'=>$i18n->get('dropdown.not_paid')); + $form->addInput(array('type'=>'combobox', + 'name'=>'mark_paid_action_options', + 'class'=>'dropdown-field-with-button', + 'data'=>$mark_paid_action_options, + 'value'=>$cl_mark_paid_action_option)); + $form->addInput(array('type'=>'submit','name'=>'btn_mark_paid','value'=>$i18n->get('button.submit'))); + $smarty->assign('use_mark_paid', true); +} + +// Controls for "Assign to invoice" block. +$smarty->assign('use_assign_to_invoice', false); +if ($useAssignToInvoice) { // Client is selected and we are displaying the invoice column. - $recent_invoices = ttTeamHelper::getRecentInvoices($user->team_id, $client_id); + $recent_invoices = ttGroupHelper::getRecentInvoices($client_id); if ($recent_invoices) { + $assign_invoice_select_options = array('1'=>$i18n->get('dropdown.all'),'2'=>$i18n->get('dropdown.select')); + $form->addInput(array('type'=>'combobox', + 'name'=>'assign_invoice_select_options', + 'class'=>'dropdown-field-with-button', + 'data'=>$assign_invoice_select_options, + 'value'=>$cl_assign_invoice_select_option)); $form->addInput(array('type'=>'combobox', 'name'=>'recent_invoice', + 'class'=>'dropdown-field-with-button', 'data'=>$recent_invoices, 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select_invoice')))); - $form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.submit'))); - $smarty->assign('use_checkboxes', true); + 'value'=>$cl_recent_invoice_option, + 'empty'=>array(''=>$i18n->get('dropdown.select_invoice')))); + $form->addInput(array('type'=>'submit','name'=>'btn_assign_invoice','value'=>$i18n->get('button.submit'))); + $smarty->assign('use_assign_to_invoice', true); } } +// Controls for "Assign to timesheet" block. +$smarty->assign('use_assign_to_timesheet', false); +if ($useAssignToTimesheet) { + $assign_timesheet_select_options = array('1'=>$i18n->get('dropdown.all'),'2'=>$i18n->get('dropdown.select')); + $form->addInput(array('type'=>'combobox', + 'name'=>'assign_timesheet_select_options', + 'class'=>'dropdown-field-with-button', + 'data'=>$assign_timesheet_select_options, + 'value'=>$cl_assign_timesheet_select_option)); + $form->addInput(array('type'=>'combobox', + 'name'=>'timesheet', + 'class'=>'dropdown-field-with-button', + 'data'=>$timesheets, + 'datakeys'=>array('id','name'), + 'value'=>$cl_timesheet_option, + 'empty'=>array(''=>$i18n->get('dropdown.select_timesheet')))); + $form->addInput(array('type'=>'submit','name'=>'btn_assign_timesheet','value'=>$i18n->get('button.submit'))); + $smarty->assign('use_assign_to_timesheet', true); +} + if ($request->isPost()) { - foreach($_POST as $key => $val) { - if ('log_id_' == substr($key, 0, 7)) - $time_log_ids[] = substr($key, 7); - if ('item_id_' == substr($key, 0, 8)) - $expense_item_ids[] = substr($key, 8); - if ('recent_invoice' == $key) - $invoice_id = $val; + + // Validate parameters and at the same time build arrays of record ids. + if (($request->getParameter('btn_mark_approved') && 2 == $request->getParameter('mark_approved_select_options')) + || ($request->getParameter('btn_mark_paid') && 2 == $request->getParameter('mark_paid_select_options')) + || ($request->getParameter('btn_assign_invoice') && 2 == $request->getParameter('assign_invoice_select_options')) + || ($request->getParameter('btn_assign_timesheet') && 2 == $request->getParameter('assign_timesheet_select_options'))) { + // We act on selected records. Are there any? + foreach($_POST as $key => $val) { + if ('log_id_' == substr($key, 0, 7)) + $time_log_ids[] = substr($key, 7); + if ('item_id_' == substr($key, 0, 8)) + $expense_item_ids[] = substr($key, 8); + } + if (!$time_log_ids && !$expense_item_ids) $err->Add($i18n->get('error.record')); // There are no selected records. + // Validation of parameteres ended here. + } else { + // We are assigning all report items. Get the arrays from session. + // Note: getting from session assures we act only on previously displayed records. + // Rebuilding from $bean may get us a different set. + $item_ids = ttReportHelper::getFromSession(); + $time_log_ids = @$item_ids['report_item_ids']; + $expense_item_ids = @$item_ids['report_item_expense_ids']; + // The above code is here because the arrays are used in both "Mark paid" and "Assign to invoice" handlers below. } - if ($time_log_ids || $expense_item_ids) { - // Some records are checked for invoice editing. Adjust their invoice accordingly. - ttReportHelper::assignToInvoice($invoice_id, $time_log_ids, $expense_item_ids); + + if ($err->no()) { + if ($request->getParameter('btn_mark_approved')) { + // User clicked the "Mark approved" button to mark some or all items either approved or not approved. + + // Determine user action. + $mark_approved = $request->getParameter('mark_approved_action_options') == 1 ? true : false; + + // Mark as requested. + if ($time_log_ids || $expense_item_ids) { + ttReportHelper::markApproved($time_log_ids, $expense_item_ids, $mark_approved); + } + + // Re-display this form. + header('Location: report.php'); + exit(); + } + + if ($request->getParameter('btn_mark_paid')) { + // User clicked the "Mark paid" button to mark some or all items either paid or not paid. + + // Determine user action. + $mark_paid = $request->getParameter('mark_paid_action_options') == 1 ? true : false; + + // Mark as requested. + if ($time_log_ids || $expense_item_ids) { + ttReportHelper::markPaid($time_log_ids, $expense_item_ids, $mark_paid); + } + + // Re-display this form. + header('Location: report.php'); + exit(); + } + + if ($request->getParameter('btn_assign_invoice')) { + // User clicked the Submit button to assign all or some items to a recent invoice. + + // Determine invoice id. + $invoice_id = $request->getParameter('recent_invoice'); + + // Assign as requested. + if ($time_log_ids || $expense_item_ids) { + ttReportHelper::assignToInvoice($invoice_id, $time_log_ids, $expense_item_ids); + } + // Re-display this form. + header('Location: report.php'); + exit(); + } + + if ($request->getParameter('btn_assign_timesheet')) { + // User clicked the Submit button to assign all or some items to a timesheet. + + // Determine invoice id. + $timesheet_id = $request->getParameter('timesheet'); + + // Assign as requested. + if ($time_log_ids) { + ttReportHelper::assignToTimesheet($timesheet_id, $time_log_ids); + } + // Re-display this form. + header('Location: report.php'); + exit(); + } } - // Re-display this form. - header('Location: report.php'); - exit(); } // isPost -$group_by = $bean->getAttribute('group_by'); +$report_items = ttReportHelper::getItems($options); +// Store record ids in session in case user wants to act on records such as marking them all paid. +if ($request->isGet() && $use_checkboxes) + ttReportHelper::putInSession($report_items); -$report_items = ttReportHelper::getItems($bean); -if ('no_grouping' != $group_by) - $subtotals = ttReportHelper::getSubtotals($bean); -$totals = ttReportHelper::getTotals($bean); +$subtotals = array(); +$smarty->assign('print_subtotals', false); +if (ttReportHelper::grouping($options)) { + $subtotals = ttReportHelper::getSubtotals($options); + $smarty->assign('group_by_header', ttReportHelper::makeGroupByHeader($options)); + if ($report_items) { + // Assign variables that are used to print subtotals. + $smarty->assign('print_subtotals', true); + $smarty->assign('first_pass', true); + $smarty->assign('prev_grouped_by', ''); + $smarty->assign('cur_grouped_by', ''); + } +} +$totals = ttReportHelper::getTotals($options); -// Assign variables that are used to print subtotals. -if ($report_items && 'no_grouping' != $group_by) { - $smarty->assign('print_subtotals', true); - $smarty->assign('first_pass', true); - $smarty->assign('group_by', $group_by); - $smarty->assign('prev_grouped_by', ''); - $smarty->assign('cur_grouped_by', ''); +// Determine column span for note field and empty rows. +$colspan = 0; +if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) $colspan++; +if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $checkbox_control_name = 'show_user_field_'.$userField['id']; + if ($bean->getAttribute($checkbox_control_name)) $colspan++; + } } -// Determine group by header. -if ('no_grouping' != $group_by) { - if ('cf_1' == $group_by) - $smarty->assign('group_by_header', $custom_fields->fields[0]['label']); - else { - $key = 'label.'.$group_by; - $smarty->assign('group_by_header', $i18n->getKey($key)); +if ($bean->getAttribute('chclient')) $colspan++; +if ($bean->getAttribute('chproject')) $colspan++; +if ($bean->getAttribute('chtask')) $colspan++; +if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $checkbox_control_name = 'show_time_field_'.$timeField['id']; + if ($bean->getAttribute($checkbox_control_name)) $colspan++; } } +if ($bean->getAttribute('chstart')) $colspan++; +if ($bean->getAttribute('chfinish')) $colspan++; +if ($bean->getAttribute('chduration')) $colspan++; +if (!$user->getConfigOption('report_note_on_separate_row') && $bean->getAttribute('chnote')) $colspan++; +if ($bean->getAttribute('chunits')) $colspan++; +if ($bean->getAttribute('chcost')) { + if ($show_cost_per_hour) + $colspan++; + $colspan++; +} +if ($bean->getAttribute('chapproved')) $colspan++; +if ($bean->getAttribute('chpaid')) $colspan++; +if ($bean->getAttribute('chip')) $colspan++; +if ($bean->getAttribute('chinvoice')) $colspan++; +if ($bean->getAttribute('chtimesheet')) $colspan++; +if ($bean->getAttribute('chfiles')) $colspan++; +if ($use_checkboxes) $colspan++; +$colspan++; // One more column for edit icons. + // Assign variables that are used to alternate color of rows for different dates. $smarty->assign('prev_date', ''); $smarty->assign('cur_date', ''); $smarty->assign('report_row_class', 'rowReportItem'); - $smarty->assign('forms', array($form->getName()=>$form->toArray())); - $smarty->assign('report_items', $report_items); $smarty->assign('subtotals', $subtotals); $smarty->assign('totals', $totals); +$smarty->assign('show_cost_per_hour', $show_cost_per_hour); +$smarty->assign('note_on_separate_row', $user->getConfigOption('report_note_on_separate_row')); +$smarty->assign('colspan', $colspan); $smarty->assign('bean', $bean); -$smarty->assign('title', $i18n->getKey('title.report').": ".$totals['start_date']." - ".$totals['end_date']); +$smarty->assign('title', $i18n->get('title.report').": ".$totals['start_date']." - ".$totals['end_date']); $smarty->assign('content_page_name', 'report.tpl'); $smarty->display('index.tpl'); diff --git a/report_send.php b/report_send.php index 9389cff24..b20dd6dfe 100644 --- a/report_send.php +++ b/report_send.php @@ -1,44 +1,20 @@ id); +$uc = new ttUserConfig(); if ($request->isPost()) { $cl_receiver = trim($request->getParameter('receiver')); @@ -46,34 +22,36 @@ $cl_subject = trim($request->getParameter('subject')); $cl_comment = trim($request->getParameter('comment')); } else { - $cl_receiver = $sc->getValue(SYSC_LAST_REPORT_EMAIL); - $cl_cc = $sc->getValue(SYSC_LAST_REPORT_CC); - $cl_subject = $i18n->getKey('form.mail.report_subject'); + $cl_receiver = $uc->getValue(SYSC_LAST_REPORT_EMAIL); + $cl_cc = $uc->getValue(SYSC_LAST_REPORT_CC); + $cl_subject = $i18n->get('form.mail.report_subject'); } $form = new Form('mailForm'); -$form->addInput(array('type'=>'text','name'=>'receiver','style'=>'width: 300px;','value'=>$cl_receiver)); -$form->addInput(array('type'=>'text','name'=>'cc','style'=>'width: 300px;','value'=>$cl_cc)); -$form->addInput(array('type'=>'text','name'=>'subject','style'=>'width: 300px;','value'=>$cl_subject)); -$form->addInput(array('type'=>'textarea','name'=>'comment','maxlength'=>'250','style'=>'width: 300px; height: 60px;')); -$form->addInput(array('type'=>'submit','name'=>'btn_send','value'=>$i18n->getKey('button.send'))); +$form->addInput(array('type'=>'text','name'=>'receiver','value'=>$cl_receiver)); +$form->addInput(array('type'=>'text','name'=>'cc','value'=>$cl_cc)); +$form->addInput(array('type'=>'text','name'=>'subject','value'=>$cl_subject)); +$form->addInput(array('type'=>'textarea','name'=>'comment','maxlength'=>'250')); +$form->addInput(array('type'=>'submit','name'=>'btn_send','value'=>$i18n->get('button.send'))); if ($request->isPost()) { // Validate user input. - if (!ttValidEmailList($cl_receiver)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.to')); - if (!ttValidEmailList($cl_cc, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.cc')); - if (!ttValidString($cl_subject)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.subject')); - if (!ttValidString($cl_comment, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.comment')); + if (!ttValidEmailList($cl_receiver)) $err->add($i18n->get('error.field'), $i18n->get('form.mail.to')); + if (!ttValidEmailList($cl_cc, true)) $err->add($i18n->get('error.field'), $i18n->get('label.cc')); + if (!ttValidString($cl_subject)) $err->add($i18n->get('error.field'), $i18n->get('label.subject')); + if (!ttValidString($cl_comment, true)) $err->add($i18n->get('error.field'), $i18n->get('label.comment')); if ($err->no()) { // Save last report emails for future use. - $sc->setValue(SYSC_LAST_REPORT_EMAIL, $cl_receiver); - $sc->setValue(SYSC_LAST_REPORT_CC, $cl_cc); + $uc->setValue(SYSC_LAST_REPORT_EMAIL, $cl_receiver); + $uc->setValue(SYSC_LAST_REPORT_CC, $cl_cc); // Obtain session bean with report attributes. $bean = new ActionForm('reportBean', new Form('reportForm')); + $options = ttReportHelper::getReportOptions($bean); + // Prepare report body. - $body = ttReportHelper::prepareReportBody($bean, $cl_comment); + $body = ttReportHelper::prepareReportBody($options, $cl_comment); import('mail.Mailer'); $mailer = new Mailer(); @@ -83,25 +61,17 @@ $mailer->setReceiver($cl_receiver); if (isset($cl_cc)) $mailer->setReceiverCC($cl_cc); + if (!empty($user->bcc_email)) + $mailer->setReceiverBCC($user->bcc_email); $mailer->setMailMode(MAIL_MODE); if ($mailer->send($cl_subject, $body)) - $msg->add($i18n->getKey('form.mail.report_sent')); + $msg->add($i18n->get('form.mail.report_sent')); else - $err->add($i18n->getKey('error.mail_send')); - } -} - -$smarty->assign('sender', SENDER); -if (function_exists('imap_mime_header_decode')) { - $elements = imap_mime_header_decode(SENDER); - if (count($elements) > 1) { - // Reassign sender. - $new = $elements[count($elements) - 2]->text; - $smarty->assign('sender', $elements[count($elements) - 2]->text); + $err->add($i18n->get('error.mail_send')); } } -$smarty->assign('title', $i18n->getKey('title.send_report')); +$smarty->assign('title', $i18n->get('title.send_report')); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.mailForm.'.($cl_receiver?'comment':'receiver').'.focus()"'); $smarty->assign('content_page_name', 'mail.tpl'); diff --git a/reports.php b/reports.php index add2b43f3..aabeec08f 100644 --- a/reports.php +++ b/reports.php @@ -1,146 +1,222 @@ exists()) { + header('Location: access_denied.php'); // No users in subgroup. + exit(); +} +// End of access checks. + +$trackingMode = $user->getTrackingMode(); // Use custom fields plugin if it is enabled. if ($user->isPluginEnabled('cf')) { require_once('plugins/CustomFields.class.php'); - $custom_fields = new CustomFields($user->team_id); + $custom_fields = new CustomFields(); $smarty->assign('custom_fields', $custom_fields); } $form = new Form('reportForm'); // Get saved favorite reports for user. -$report_list = ttFavReportHelper::getReports($user->id); +$report_list = ttFavReportHelper::getReports(); $form->addInput(array('type'=>'combobox', 'name'=>'favorite_report', - 'onchange'=>'document.reportForm.fav_report_changed.value=1;document.reportForm.submit();', - 'style'=>'width: 250px;', + 'onchange'=>'this.form.fav_report_changed.value=1;this.form.submit();', 'data'=>$report_list, 'datakeys'=>array('id','name'), - 'empty'=>array('-1'=>$i18n->getKey('dropdown.no')))); + 'empty'=>array('-1'=>$i18n->get('dropdown.no')))); $form->addInput(array('type'=>'hidden','name'=>'fav_report_changed')); // Generate and Delete buttons. -$form->addInput(array('type'=>'submit','name'=>'btn_generate','value'=>$i18n->getKey('button.generate'))); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'),'onclick'=>"return confirm('".$i18n->getKey('form.reports.confirm_delete')."')")); +$form->addInput(array('type'=>'submit','name'=>'btn_generate','value'=>$i18n->get('button.generate'))); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'),'onclick'=>"return confirm('".$i18n->get('form.reports.confirm_delete')."')")); // Dropdown for clients if the clients plugin is enabled. -if ($user->isPluginEnabled('cl') && !($user->isClient() && $user->client_id)) { - if ($user->canManageTeam() || ($user->isClient() && !$user->client_id)) - $client_list = ttClientHelper::getClients($user->team_id); - else +$showClient = $user->isPluginEnabled('cl') && !$user->isClient(); +$client_list = array(); +if ($showClient) { + if ($user->can('view_reports') || $user->can('view_all_reports')) { + $client_list = ttClientHelper::getClients(); // TODO: improve getClients for "view_reports" + // by filtering out not relevant clients. + } else $client_list = ttClientHelper::getClientsForUser(); + if (count($client_list) == 0) $showClient = false; +} +if ($showClient) { $form->addInput(array('type'=>'combobox', + 'onchange'=>'fillProjectDropdown(this.value);', 'name'=>'client', - 'style'=>'width: 250px;', 'data'=>$client_list, 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.all')))); + 'empty'=>array(''=>$i18n->get('dropdown.all')))); } -// If we have a TYPE_DROPDOWN custom field - add control to select an option. -if ($custom_fields && $custom_fields->fields[0] && $custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) { - $form->addInput(array('type'=>'combobox','name'=>'option', - 'style'=>'width: 250px;', - 'value'=>$cl_cf_1, - 'data'=>$custom_fields->options, - 'empty'=>array(''=>$i18n->getKey('dropdown.all')))); +// Add project dropdown. +$showProject = MODE_PROJECTS == $trackingMode || MODE_PROJECTS_AND_TASKS == $trackingMode; +$project_list = array(); +if ($showProject) { + if ($user->can('view_reports') || $user->can('view_all_reports')) { + $config = new ttConfigHelper($user->getConfig()); + $includeInactiveProjects = $config->getDefinedValue('report_inactive_projects'); + $project_list = ttProjectHelper::getProjects($includeInactiveProjects); + } elseif ($user->isClient()) { + $project_list = ttProjectHelper::getProjectsForClient(); + } else { + $project_list = ttProjectHelper::getAssignedProjects($user->getUser()); + } + if (count($project_list) == 0) $showProject = false; +} +if ($showProject) { + $form->addInput(array('type'=>'multipleselectcombobox', + 'onchange'=>'fillTaskDropdown();selectAssignedUsers();', + 'name'=>'project', // Multiple select now, but the name without "[]" in the end because we can't use it in reports.tpl. + // Example: {$forms.reportForm.project.control}, we can't use {$forms.reportForm.project[].control}. + // This creates an ugly complication: + // Form.class.php addInput adds "[]" to a multiple select combobox element name. + // When we verify bean in a form post, the name is "project". + // But if bean is loaded from session, the name is "project[]". + // It is unclear how to deal with this properly. + //'multiple'=>true, + 'data'=>$project_list, + 'datakeys'=>array('id','name'), + 'empty'=>array(''=>$i18n->get('dropdown.all')))); } -// Add controls for projects and tasks. -if ($user->canManageTeam()) { - $project_list = ttProjectHelper::getProjects(); // Manager and co-managers can run reports on all active and inactive projects. -} elseif ($user->isClient()) { - $project_list = ttProjectHelper::getProjectsForClient(); -} else { - $project_list = ttProjectHelper::getAssignedProjects($user->id); +// Add task dropdown. +$showTask = MODE_PROJECTS_AND_TASKS == $trackingMode; +$task_list = array(); +if ($showTask) { + $task_list = ttGroupHelper::getActiveTasks(); + if (count($task_list) == 0) $showTask = false; } -$form->addInput(array('type'=>'combobox', - 'onchange'=>'fillTaskDropdown(this.value);selectAssignedUsers(this.value);', - 'name'=>'project', - 'style'=>'width: 250px;', - 'data'=>$project_list, - 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.all')))); -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - $task_list = ttTeamHelper::getActiveTasks($user->team_id); +if ($showTask) { $form->addInput(array('type'=>'combobox', 'name'=>'task', - 'style'=>'width: 250px;', 'data'=>$task_list, 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.all')))); + 'empty'=>array(''=>$i18n->get('dropdown.all')))); } -// Add include records control. -$include_options = array('1'=>$i18n->getKey('form.reports.include_billable'), - '2'=>$i18n->getKey('form.reports.include_not_billable')); -$form->addInput(array('type'=>'combobox', - 'name'=>'include_records', - 'style'=>'width: 250px;', - 'data'=>$include_options, - 'empty'=>array(''=>$i18n->getKey('dropdown.all')))); +// Add billable dropdown. +$showBillable = $user->isPluginEnabled('iv'); +if ($showBillable) { + $include_options = array('1'=>$i18n->get('form.reports.include_billable'), + '2'=>$i18n->get('form.reports.include_not_billable')); + $form->addInput(array('type'=>'combobox', + 'name'=>'include_records', // TODO: how about a better name here? + 'data'=>$include_options, + 'empty'=>array(''=>$i18n->get('dropdown.all')))); +} // Add invoiced / not invoiced selector. -$invoice_options = array('1'=>$i18n->getKey('form.reports.include_invoiced'), - '2'=>$i18n->getKey('form.reports.include_not_invoiced')); -$form->addInput(array('type'=>'combobox', - 'name'=>'invoice', - 'style'=>'width: 250px;', - 'data'=>$invoice_options, - 'empty'=>array(''=>$i18n->getKey('dropdown.all')))); +$showInvoiceDropdown = $user->isPluginEnabled('iv') && $user->can('manage_invoices'); +if ($showInvoiceDropdown) { + $invoice_options = array('1'=>$i18n->get('form.reports.include_invoiced'), + '2'=>$i18n->get('form.reports.include_not_invoiced')); + $form->addInput(array('type'=>'combobox', + 'name'=>'invoice', + 'data'=>$invoice_options, + 'empty'=>array(''=>$i18n->get('dropdown.all')))); +} +$showInvoiceCheckbox = $user->isPluginEnabled('iv') && ($user->can('manage_invoices') || $user->isClient()); + +// Add paid status selector. +$showPaidStatus = $user->isPluginEnabled('ps') && $user->can('manage_invoices'); +if ($showPaidStatus) { + $form->addInput(array('type'=>'combobox', + 'name'=>'paid_status', + 'data'=>array('1'=>$i18n->get('dropdown.paid'),'2'=>$i18n->get('dropdown.not_paid')), + 'empty'=>array(''=>$i18n->get('dropdown.all')) + )); +} + +// Add approved / not approved selector. +$showApproved = $user->isPluginEnabled('ap') && + ($user->can('view_own_reports') || $user->can('view_reports') || + $user->can('view_all_reports') || ($user->can('view_client_reports') && $user->can('view_client_unapproved'))); +if ($showApproved) { + $form->addInput(array('type'=>'combobox', + 'name'=>'approved', + 'data'=>array('1'=>$i18n->get('dropdown.approved'),'2'=>$i18n->get('dropdown.not_approved')), + 'empty'=>array(''=>$i18n->get('dropdown.all')) + )); +} -$user_list = array(); -if ($user->canManageTeam() || $user->isClient()) { +// Add timesheet assignment selector. +$showTimesheetDropdown = $user->isPluginEnabled('ts'); +if ($showTimesheetDropdown) { + $form->addInput(array('type'=>'combobox', + 'name'=>'timesheet', + 'data'=>array(TIMESHEET_NOT_ASSIGNED=>$i18n->get('form.reports.include_not_assigned'), + TIMESHEET_ASSIGNED=>$i18n->get('form.reports.include_assigned'), + TIMESHEET_PENDING=>$i18n->get('form.reports.include_pending'), + TIMESHEET_APPROVED=>$i18n->get('dropdown.approved'), + TIMESHEET_NOT_APPROVED=>$i18n->get('dropdown.not_approved')), + 'empty'=>array(''=>$i18n->get('dropdown.all')) + )); +} +$showTimesheetCheckbox = $user->isPluginEnabled('ts'); + +// Add user table. +$showUsers = $user->can('view_reports') || $user->can('view_all_reports') || $user->isClient(); +$user_list = $user_list_active = $user_list_inactive = array(); +if ($showUsers) { // Prepare user and assigned projects arrays. - if ($user->canManageTeam()) - $users = ttTeamHelper::getUsers(); // Active and inactive users for managers. - elseif ($user->isClient()) - $users = ttTeamHelper::getUsersForClient(); // Active and inactive users for clients. + if ($user->can('view_reports') || $user->can('view_all_reports')) { + $rank = $user->getMaxRankForGroup($user->getGroup()); + if ($user->can('view_all_reports')) $max_rank = MAX_RANK; + if ($user->can('view_own_reports')) { + $options_active = array('max_rank'=>$max_rank,'include_self'=>true,'status'=>ACTIVE); + $options_inactive = array('max_rank'=>$max_rank,'include_self'=>true,'status'=>INACTIVE); + } else { + $options_active = array('max_rank'=>$max_rank,'status'=>ACTIVE); + $options_inactive = array('max_rank'=>$max_rank,'status'=>INACTIVE); + } + $active_users = $user->getUsers($options_active); + $inactive_users = $user->getUsers($options_inactive); + } + elseif ($user->isClient()) { + $options_active = array('status'=>ACTIVE); + $options_inactive = array('status'=>INACTIVE); + $active_users = ttGroupHelper::getUsersForClient($options_active); + $inactive_users = ttGroupHelper::getUsersForClient($options_inactive); + } + + foreach ($active_users as $single_user) { + $user_list_active[$single_user['id']] = $single_user['name']; + $projects = ttProjectHelper::getAssignedProjects($single_user['id']); + if ($projects) { + foreach ($projects as $single_project) { + $assigned_projects[$single_user['id']][] = $single_project['id']; + } + } + } + $row_count = is_array($user_list_active) ? ceil(count($user_list_active)/3) : 1; + $form->addInput(array('type'=>'checkboxgroup', + 'name'=>'users_active', + 'data'=>$user_list_active, + 'layout'=>'V', + 'groupin'=>$row_count)); - foreach ($users as $single_user) { - $user_list[$single_user['id']] = $single_user['name']; + foreach ($inactive_users as $single_user) { + $user_list_inactive[$single_user['id']] = $single_user['name']; $projects = ttProjectHelper::getAssignedProjects($single_user['id']); if ($projects) { foreach ($projects as $single_project) { @@ -148,177 +224,315 @@ } } } - $row_count = ceil(count($user_list)/3); + $row_count = ceil(count($user_list_inactive)/3); $form->addInput(array('type'=>'checkboxgroup', - 'name'=>'users', - 'data'=>$user_list, + 'name'=>'users_inactive', + 'data'=>$user_list_inactive, 'layout'=>'V', - 'groupin'=>$row_count, - 'style'=>'width: 100%;')); + 'groupin'=>$row_count)); } // Add control for time period. $form->addInput(array('type'=>'combobox', 'name'=>'period', - 'style'=>'width: 250px;', - 'data'=>array(INTERVAL_THIS_MONTH=>$i18n->getKey('dropdown.this_month'), - INTERVAL_LAST_MONTH=>$i18n->getKey('dropdown.last_month'), - INTERVAL_THIS_WEEK=>$i18n->getKey('dropdown.this_week'), - INTERVAL_LAST_WEEK=>$i18n->getKey('dropdown.last_week')), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'data'=>array(INTERVAL_THIS_MONTH=>$i18n->get('dropdown.current_month'), + INTERVAL_PREVIOUS_MONTH=>$i18n->get('dropdown.previous_month'), + INTERVAL_THIS_WEEK=>$i18n->get('dropdown.current_week'), + INTERVAL_PREVIOUS_WEEK=>$i18n->get('dropdown.previous_week'), + INTERVAL_THIS_DAY=>$i18n->get('dropdown.current_day'), + INTERVAL_PREVIOUS_DAY=>$i18n->get('dropdown.previous_day')), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); // Add controls for start and end dates. $form->addInput(array('type'=>'datefield','maxlength'=>'20','name'=>'start_date')); $form->addInput(array('type'=>'datefield','maxlength'=>'20','name'=>'end_date')); -// Add checkboxes for fields. -if ($user->isPluginEnabled('cl')) - $form->addInput(array('type'=>'checkbox','name'=>'chclient','data'=>1)); -if (($user->canManageTeam() || $user->isClient()) && $user->isPluginEnabled('iv')) - $form->addInput(array('type'=>'checkbox','name'=>'chinvoice','data'=>1)); -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) - $form->addInput(array('type'=>'checkbox','name'=>'chproject','data'=>1)); -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) - $form->addInput(array('type'=>'checkbox','name'=>'chtask','data'=>1)); -if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) { - $form->addInput(array('type'=>'checkbox','name'=>'chstart','data'=>1)); - $form->addInput(array('type'=>'checkbox','name'=>'chfinish','data'=>1)); +// Add control for notes containing. +$form->addInput(array('type'=>'text','maxlength'=>'80','name'=>'note_containing')); + +// Add checkboxes for "Show fields" block. +if ($showClient) + $form->addInput(array('type'=>'checkbox','name'=>'chclient')); +if ($showProject) + $form->addInput(array('type'=>'checkbox','name'=>'chproject')); +if ($showTask) + $form->addInput(array('type'=>'checkbox','name'=>'chtask')); +if ($showInvoiceCheckbox) + $form->addInput(array('type'=>'checkbox','name'=>'chinvoice')); +if ($showPaidStatus) + $form->addInput(array('type'=>'checkbox','name'=>'chpaid')); +$showIP = $user->can('view_reports') || $user->can('view_all_reports'); +if ($showIP) + $form->addInput(array('type'=>'checkbox','name'=>'chip')); +$recordType = $user->getRecordType(); +$showStart = TYPE_START_FINISH == $recordType || TYPE_ALL == $recordType; +$showFinish = $showStart; +if ($showStart) + $form->addInput(array('type'=>'checkbox','name'=>'chstart')); +if ($showFinish) + $form->addInput(array('type'=>'checkbox','name'=>'chfinish')); +$form->addInput(array('type'=>'checkbox','name'=>'chduration')); +$form->addInput(array('type'=>'checkbox','name'=>'chnote')); +$form->addInput(array('type'=>'checkbox','name'=>'chcost')); +$showWorkUnits = $user->isPluginEnabled('wu'); +if ($showWorkUnits) + $form->addInput(array('type'=>'checkbox','name'=>'chunits')); +if ($showTimesheetCheckbox) + $form->addInput(array('type'=>'checkbox','name'=>'chtimesheet')); +if ($showApproved) + $form->addInput(array('type'=>'checkbox','name'=>'chapproved')); +$showFiles = $user->isPluginEnabled('at'); +if ($showFiles) + $form->addInput(array('type'=>'checkbox','name'=>'chfiles')); + +// Add a hidden control for timesheet_user_id (who to generate a timesheet for). +if ($showTimesheetCheckbox) + $form->addInput(array('type'=>'hidden','name'=>'timesheet_user_id')); + +// If we have time custom fields - add controls for them. +if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + $checkbox_field_name = 'show_'.$field_name; + if ($timeField['type'] == CustomFields::TYPE_TEXT) { + $form->addInput(array('type'=>'text','name'=>$field_name)); + } elseif ($timeField['type'] == CustomFields::TYPE_DROPDOWN) { + $form->addInput(array('type'=>'combobox','name'=>$field_name, + 'data'=>CustomFields::getOptions($timeField['id']), + 'empty'=>array(''=>$i18n->get('dropdown.all')))); + } + // Also add a checkbox (to print the field or not). + $form->addInput(array('type'=>'checkbox','name'=>$checkbox_field_name)); + } +} + +// If we have user custom fields - add controls for them. +if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + $checkbox_field_name = 'show_'.$field_name; + if ($userField['type'] == CustomFields::TYPE_TEXT) { + $form->addInput(array('type'=>'text','name'=>$field_name,)); + } elseif ($userField['type'] == CustomFields::TYPE_DROPDOWN) { + $form->addInput(array('type'=>'combobox','name'=>$field_name, + 'data'=>CustomFields::getOptions($userField['id']), + 'empty'=>array(''=>$i18n->get('dropdown.all')))); + } + // Also add a checkbox (to print the field or not). + $form->addInput(array('type'=>'checkbox','name'=>$checkbox_field_name)); + } +} + +// If we have projects custom fields - add controls for them. +if ($showProject && isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_field_name = 'show_'.$field_name; + if ($projectField['type'] == CustomFields::TYPE_TEXT) { + $form->addInput(array('type'=>'text','name'=>$field_name,)); + } elseif ($projectField['type'] == CustomFields::TYPE_DROPDOWN) { + $form->addInput(array('type'=>'combobox','name'=>$field_name, + 'data'=>CustomFields::getOptions($projectField['id']), + 'empty'=>array(''=>$i18n->get('dropdown.all')))); + } + // Also add a checkbox (to print the field or not). + $form->addInput(array('type'=>'checkbox','name'=>$checkbox_field_name)); + } } -$form->addInput(array('type'=>'checkbox','name'=>'chduration','data'=>1)); -$form->addInput(array('type'=>'checkbox','name'=>'chnote','data'=>1)); -if (defined('COST_ON_REPORTS') && isTrue(COST_ON_REPORTS)) - $form->addInput(array('type'=>'checkbox','name'=>'chcost','data'=>1)); -// If we have a custom field - add a checkbox for it. -if ($custom_fields && $custom_fields->fields[0]) - $form->addInput(array('type'=>'checkbox','name'=>'chcf_1','data'=>1)); // Add group by control. -$group_by_options['no_grouping'] = $i18n->getKey('form.reports.group_by_no'); -$group_by_options['date'] = $i18n->getKey('form.reports.group_by_date'); -if ($user->canManageTeam() || $user->isClient()) - $group_by_options['user'] = $i18n->getKey('form.reports.group_by_user'); +$group_by_options['no_grouping'] = $i18n->get('form.reports.group_by_no'); +$group_by_options['date'] = $i18n->get('form.reports.group_by_date'); +if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) + $group_by_options['user'] = $i18n->get('form.reports.group_by_user'); if ($user->isPluginEnabled('cl') && !($user->isClient() && $user->client_id)) - $group_by_options['client'] = $i18n->getKey('form.reports.group_by_client'); -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) - $group_by_options['project'] = $i18n->getKey('form.reports.group_by_project'); -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) - $group_by_options['task'] = $i18n->getKey('form.reports.group_by_task'); -if ($custom_fields && $custom_fields->fields[0] && $custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) { - $group_by_options['cf_1'] = $custom_fields->fields[0]['label']; + $group_by_options['client'] = $i18n->get('form.reports.group_by_client'); +if (MODE_PROJECTS == $trackingMode || MODE_PROJECTS_AND_TASKS == $trackingMode) + $group_by_options['project'] = $i18n->get('form.reports.group_by_project'); +if (MODE_PROJECTS_AND_TASKS == $trackingMode) + $group_by_options['task'] = $i18n->get('form.reports.group_by_task'); +// If we have time custom fields - add group by options for them. +if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + $group_by_options[$field_name] = $timeField['label']; + } +} +// If we have user custom fields - add group by options for them. +if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + $group_by_options[$field_name] = $userField['label']; + } +} +// If we have project custom fields - add group by options for them. +if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $group_by_options[$field_name] = $projectField['label']; + } } -$form->addInput(array('type'=>'combobox','onchange'=>'handleCheckboxes();','name'=>'group_by','data'=>$group_by_options)); -$form->addInput(array('type'=>'checkbox','name'=>'chtotalsonly','data'=>1)); +$group_by_options_size = sizeof($group_by_options); +$form->addInput(array('type'=>'combobox','onchange'=>'handleCheckboxes();','name'=>'group_by1','data'=>$group_by_options)); +if ($group_by_options_size > 2) $form->addInput(array('type'=>'combobox','onchange'=>'handleCheckboxes();','name'=>'group_by2','data'=>$group_by_options)); +if ($group_by_options_size > 3) $form->addInput(array('type'=>'combobox','onchange'=>'handleCheckboxes();','name'=>'group_by3','data'=>$group_by_options)); +$form->addInput(array('type'=>'checkbox','name'=>'chtotalsonly')); // Add text field for a new favorite report name. -$form->addInput(array('type'=>'text','name'=>'new_fav_report','maxlength'=>'30','style'=>'width: 250px;')); +$form->addInput(array('type'=>'text','name'=>'new_fav_report','maxlength'=>'30')); // Save button. -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_generate','value'=>$i18n->getKey('button.generate'))); +$form->addInput(array('type'=>'submit','name'=>'btn_generate','value'=>$i18n->get('button.generate'))); // Create a bean (which is a mechanism to remember form values in session). $bean = new ActionForm('reportBean', $form, $request); // At this point form values are obtained from session if they are there. -if (($request->getMethod() == 'GET') && !$bean->isSaved()) { +if ($request->isGet() && !$bean->isSaved()) { // No previous form data were found in session. Use the following default values. - $form->setValueByElement('users', array_keys($user_list)); - $period = new Period(INTERVAL_THIS_MONTH, new DateAndTime($user->date_format)); - $form->setValueByElement('start_date', $period->getBeginDate()); - $form->setValueByElement('end_date', $period->getEndDate()); - $form->setValueByElement('chclient', 1); - $form->setValueByElement('chinvoice', 0); - $form->setValueByElement('chproject', 1); - $form->setValueByElement('chstart', 1); - $form->setValueByElement('chduration', 1); - $form->setValueByElement('chcost', 0); - $form->setValueByElement('chtask', 1); - $form->setValueByElement('chfinish', 1); - $form->setValueByElement('chnote', 1); - $form->setValueByElement('chcf_1', 0); - $form->setValueByElement('chtotalsonly', 0); + $form->setValueByElement('users_active', array_keys((array)$user_list_active)); + $period = new ttPeriod(new ttDate(), INTERVAL_THIS_MONTH); + $form->setValueByElement('start_date', $period->getStartDate($user->getDateFormat())); + $form->setValueByElement('end_date', $period->getEndDate($user->getDateFormat())); + + $form->setValueByElement('chclient', '1'); + $form->setValueByElement('chstart', '1'); + $form->setValueByElement('chfinish', '1'); + $form->setValueByElement('chduration', '1'); + + $form->setValueByElement('chproject', '1'); + $form->setValueByElement('chtask', '1'); + $form->setValueByElement('chnote', '1'); + $form->setValueByElement('chcost', '0'); + + $form->setValueByElement('chtimesheet', '0'); + $form->setValueByElement('chip', '0'); + $form->setValueByElement('chapproved', '0'); + $form->setValueByElement('chpaid', '0'); + + $form->setValueByElement('chunits', '0'); + $form->setValueByElement('chinvoice', '0'); + $form->setValueByElement('chfiles', '1'); + + $form->setValueByElement('chtotalsonly', '0'); } $form->setValueByElement('fav_report_changed',''); // Disable the Delete button when no favorite report is selected. if (!$bean->getAttribute('favorite_report') || ($bean->getAttribute('favorite_report') == -1)) - $form->getElement('btn_delete')->setEnable(false); + $form->getElement('btn_delete')->setEnabled(false); if ($request->isPost()) { - if((!$bean->getAttribute('btn_generate') && ($request->getParameter('fav_report_changed')))) { - // User changed favorite report. We need to load new values into the form. - if ($bean->getAttribute('favorite_report')) { - // This loads new favorite report options into the bean (into our form). - ttFavReportHelper::loadReport($user->id, $bean); - - // If user selected no favorite report - mark all user checkboxes (most probable scenario). - if ($bean->getAttribute('favorite_report') == -1) - $form->setValueByElement('users', array_keys($user_list)); + // Verify bean. Do not proceed with bogus post data. + if (!ttReportHelper::verifyBean($bean)) $err->add($i18n->get('error.sys')); - // Save form data in session for future use. - $bean->saveBean(); - header('Location: reports.php'); - exit(); - } - } elseif ($bean->getAttribute('btn_save')) { - // User clicked the Save button. We need to save form options as new favorite report. - if (!ttValidString($bean->getAttribute('new_fav_report'))) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.reports.save_as_favorite')); - - if ($err->no()) { - $id = ttFavReportHelper::saveReport($user->id, $bean); - if (!$id) - $err->add($i18n->getKey('error.db')); - if ($err->no()) { - $bean->setAttribute('favorite_report', $id); + if($err->no()) { + if (!$bean->getAttribute('btn_generate') && $request->getParameter('fav_report_changed')) { + // User changed favorite report. We need to load new values into the form. + if ($bean->getAttribute('favorite_report')) { + // This loads new favorite report options into the bean (into our form). + ttFavReportHelper::loadReport($bean); + + // If user selected no favorite report - mark all user checkboxes (most probable scenario). + if ($bean->getAttribute('favorite_report') == -1) { + $form->setValueByElement('users_active', array_keys($user_list_active)); + $form->setValueByElement('users_inactive', false); + } + + // Save form data in session for future use. $bean->saveBean(); header('Location: reports.php'); exit(); } - } - } elseif($bean->getAttribute('btn_delete')) { - // Delete button pressed. User wants to delete a favorite report. - if ($bean->getAttribute('favorite_report')) { - ttFavReportHelper::deleteReport($bean->getAttribute('favorite_report')); - // Load default report. - $bean->setAttribute('favorite_report',''); - $bean->setAttribute('new_fav_report', $report_list[0]['name']); - ttFavReportHelper::loadReport($user->id, $bean); - $form->setValueByElement('users', array_keys($user_list)); - $bean->saveBean(); - header('Location: reports.php'); - exit(); - } - } else { - // Generate button pressed. Check some values. - if (!$bean->getAttribute('period')) { - $start_date = new DateAndTime($user->date_format, $bean->getAttribute('start_date')); - - if ($start_date->isError() || !$bean->getAttribute('start_date')) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.start_date')); + } elseif ($bean->getAttribute('btn_save')) { + // User clicked the Save button. We need to save form options as new favorite report. - $end_date = new DateAndTime($user->date_format, $bean->getAttribute('end_date')); - if ($end_date->isError() || !$bean->getAttribute('end_date')) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.end_date')); + // We check new_fav_report here, rather than in ttReportHelper::verifyBean to display a specific error, instead of 'error.sys'. + if (!ttValidString($bean->getAttribute('new_fav_report'))) $err->add($i18n->get('error.field'), $i18n->get('form.reports.save_as_favorite')); - if ($start_date->compare($end_date) > 0) - $err->add($i18n->getKey('error.interval'), $i18n->getKey('label.end_date'), $i18n->getKey('label.start_date')); - } - - $bean->saveBean(); + if ($err->no()) { + $id = ttFavReportHelper::saveReport($bean); + if (!$id) + $err->add($i18n->get('error.db')); + if ($err->no()) { + $bean->setAttribute('favorite_report', $id); + $bean->saveBean(); + header('Location: reports.php'); + exit(); + } + } + } elseif($bean->getAttribute('btn_delete')) { + // Delete button pressed. User wants to delete a favorite report. + if ($bean->getAttribute('favorite_report')) { + ttFavReportHelper::deleteReport($bean->getAttribute('favorite_report')); + // Load default report. + $bean->setAttribute('favorite_report',''); + $bean->setAttribute('new_fav_report', $report_list[0]['name']); + ttFavReportHelper::loadReport($bean); + $form->setValueByElement('users', array_keys($user_list)); + $bean->saveBean(); + header('Location: reports.php'); + exit(); + } + } else { + // Generate button pressed. Check some values. + if (!$bean->getAttribute('period')) { + // Validate start date. + if (!ttValidDate($bean->getAttribute('start_date'))) $err->add($i18n->get('error.field'), $i18n->get('label.start_date')); + // Validate end date. + if (!ttValidDate($bean->getAttribute('end_date'))) $err->add($i18n->get('error.field'), $i18n->get('label.end_date')); + // Make sure that the end date is equal or after the start date. + if ($err->no()) { + $startDate = new ttDate($bean->getAttribute('start_date'), $user->getDateFormat()); + $endDate = new ttDate($bean->getAttribute('end_date'), $user->getDateFormat()); + if ($startDate->compare($endDate) > 0) { + $err->add($i18n->get('error.interval'), $i18n->get('label.end_date'), $i18n->get('label.start_date')); + } + } + } + $group_by1 = $bean->getAttribute('group_by1'); + $group_by2 = $bean->getAttribute('group_by2'); + $group_by3 = $bean->getAttribute('group_by3'); + if (($group_by3 != null && $group_by3 != 'no_grouping') && ($group_by3 == $group_by1 || $group_by3 == $group_by2)) + $err->add($i18n->get('error.field'), $i18n->get('form.reports.group_by')); + if (($group_by2 != null && $group_by2 != 'no_grouping') && ($group_by2 == $group_by1 || $group_by3 == $group_by2)) + $err->add($i18n->get('error.field'), $i18n->get('form.reports.group_by')); + // TODO: review the above checks and possibly move some of them to verifyBean that we call when entering isPost above. - if ($err->no()) { - // Now we can go ahead and create a report. - header('Location: report.php'); - exit(); + if ($err->no()) { + $bean->saveBean(); + // Now we can go ahead and create a report. + header('Location: report.php'); + exit(); + } } - } + } // if($err->no()) } // isPost +$smarty->assign('client_list', $client_list); +$smarty->assign('show_client', $showClient); +$smarty->assign('show_project', $showProject); +$smarty->assign('show_task', $showTask); +$smarty->assign('show_billable', $showBillable); +$smarty->assign('show_approved', $showApproved); +$smarty->assign('show_invoice_dropdown', $showInvoiceDropdown); +$smarty->assign('show_invoice_checkbox', $showInvoiceCheckbox); +$smarty->assign('show_paid_status', $showPaidStatus); +$smarty->assign('show_timesheet_dropdown', $showTimesheetDropdown); +$smarty->assign('show_timesheet_checkbox', $showTimesheetCheckbox); +$smarty->assign('show_active_users', $showUsers && $active_users); +$smarty->assign('show_inactive_users', $showUsers && $inactive_users); +$smarty->assign('show_start', $showStart); +$smarty->assign('show_finish', $showFinish); +$smarty->assign('show_work_units', $showWorkUnits); +$smarty->assign('show_ip', $showIP); +$smarty->assign('show_files', $showFiles); $smarty->assign('project_list', $project_list); $smarty->assign('task_list', $task_list); $smarty->assign('assigned_projects', $assigned_projects); $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="handleCheckboxes()"'); -$smarty->assign('title', $i18n->getKey('title.reports')); +$smarty->assign('onload', 'onLoad="handleCheckboxes();fillDropdowns()"'); +$smarty->assign('title', $i18n->get('title.reports')); $smarty->assign('content_page_name', 'reports.tpl'); $smarty->display('index.tpl'); diff --git a/role_add.php b/role_add.php new file mode 100644 index 000000000..b4e05f903 --- /dev/null +++ b/role_add.php @@ -0,0 +1,60 @@ +isPost()) { + $cl_name = trim($request->getParameter('name')); + $cl_description = trim($request->getParameter('description')); + $cl_rank = (int)$request->getParameter('rank'); +} + +$form = new Form('roleForm'); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +for ($i = 0; $i < $user->rank; $i++) { + $rank_data[] = $i; +} +$form->addInput(array('type'=>'combobox','class'=>'dropdown-field-short','name'=>'rank','data'=>$rank_data)); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.submit'))); + +if ($request->isPost()) { + // Validate user input. + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + if ($cl_rank >= $user->rank || $cl_rank < 0) $err->add($i18n->get('error.field'), $i18n->get('form.roles.rank')); + if ($err->no() && ttRoleHelper::getRoleByName($cl_name)) $err->add($i18n->get('error.object_exists')); + + if ($err->no()) { + $existing_role = ttRoleHelper::getRoleByRank($cl_rank); + if (!$existing_role) { + // Insert a role with default user rights. + if (ttRoleHelper::insert(array( + 'name' => $cl_name, + 'rank' => $cl_rank, + 'description' => $cl_description, + 'rights' => 'track_own_time,track_own_expenses,view_own_reports,view_own_charts,manage_own_settings,view_users', // Default user rights. + 'status' => ACTIVE))) { + header('Location: roles.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } else + $err->add($i18n->get('error.role_exists')); + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.add_role')); +$smarty->assign('content_page_name', 'role_add.tpl'); +$smarty->display('index.tpl'); diff --git a/role_delete.php b/role_delete.php new file mode 100644 index 000000000..e2f0805eb --- /dev/null +++ b/role_delete.php @@ -0,0 +1,50 @@ +getParameter('id'); +$role = ttRoleHelper::get($cl_role_id); +if (!$role) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$role_to_delete = $role['name']; + +$form = new Form('roleDeleteForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_role_id)); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); + +if ($request->isPost()) { + if ($request->getParameter('btn_delete')) { + if(ttRoleHelper::get($cl_role_id)) { + if (ttRoleHelper::delete($cl_role_id)) { + header('Location: roles.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } else + $err->add($i18n->get('error.db')); + } elseif ($request->getParameter('btn_cancel')) { + header('Location: roles.php'); + exit(); + } +} // isPost + +$smarty->assign('role_to_delete', $role_to_delete); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.taskDeleteForm.btn_cancel.focus()"'); +$smarty->assign('title', $i18n->get('title.delete_role')); +$smarty->assign('content_page_name', 'role_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/role_edit.php b/role_edit.php new file mode 100644 index 000000000..f2aecadee --- /dev/null +++ b/role_edit.php @@ -0,0 +1,120 @@ +getParameter('id'); +$role = ttRoleHelper::get($cl_role_id); +if (!$role) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$assigned_rights = explode(',', $role['rights']); +$available_rights = array_diff($user->rights, $assigned_rights); + +if ($request->isPost()) { + $cl_name = trim($request->getParameter('name')); + $cl_description = trim($request->getParameter('description')); + $cl_rank = $request->getParameter('rank'); + $cl_status = $request->getParameter('status'); +} else { + $cl_name = $role['name']; + $cl_description = $role['description']; + $cl_rank = $role['rank']; + $cl_status = $role['status']; +} + +$form = new Form('roleForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_role_id)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +for ($i = 0; $i < $user->rank; $i++) { + $rank_data[] = $i; +} +$form->addInput(array('type'=>'combobox','class'=>'dropdown-field-short','name'=>'rank','data'=>$rank_data,'value'=>$cl_rank)); +$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status, + 'data'=>array(ACTIVE=>$i18n->get('dropdown.status_active'),INACTIVE=>$i18n->get('dropdown.status_inactive')))); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); + +// Multiple select controls for assigned and available rights. +$form->addInput(array('type'=>'combobox','name'=>'assigned_rights','multiple'=>true,'data'=>$assigned_rights)); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('button.delete'))); +$form->addInput(array('type'=>'combobox','name'=>'available_rights','multiple'=>true,'data'=>$available_rights)); +$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); + +if ($request->isPost()) { + if ($request->getParameter('btn_save')) { + // Validate user input. + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + if ($cl_rank >= $user->rank || $cl_rank < 0) $err->add($i18n->get('error.field'), $i18n->get('form.roles.rank')); + if (!ttValidStatus($cl_status)) $err->add($i18n->get('error.field'), $i18n->get('label.status')); + + if ($err->no()) { + $existing_role = ttRoleHelper::getRoleByName($cl_name); + if (!$existing_role || ($cl_role_id == $existing_role['id'])) { + // Update role information. + if (ttRoleHelper::update(array( + 'id' => $cl_role_id, + 'name' => $cl_name, + 'rank' => $cl_rank, + 'description' => $cl_description, + 'status' => $cl_status))) { + header('Location: roles.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } else + $err->add($i18n->get('error.object_exists')); + } + } + if ($request->getParameter('btn_delete') && $request->getParameter('assigned_rights')) { + $rights = $role['rights']; + $to_delete = $request->getParameter('assigned_rights'); + foreach($to_delete as $index) { + $right_to_delete = $assigned_rights[$index]; + $rights = str_replace($right_to_delete, '', $rights); + $rights = str_replace(',,',',', $rights); + } + $rights = trim($rights, ','); + if (ttRoleHelper::update(array('id' => $cl_role_id,'rights'=> $rights))) { + header('Location: role_edit.php?id='.$role['id']); + exit(); + } else + $err->add($i18n->get('error.db')); + } + if ($request->getParameter('btn_add') && $request->getParameter('available_rights')) { + $rights = $role['rights']; + $to_add = $request->getParameter('available_rights'); + foreach($to_add as $index) { + $right_to_add = $available_rights[$index]; + // Just in case remove it. + $rights = str_replace($right_to_add, '', $rights); + $rights = str_replace(',,',',', $rights); + // Add the right only if we have it ourselves. + if (in_array($right_to_add, $user->rights)) + $rights .= ','.$right_to_add; + } + $rights = trim($rights, ','); + if (ttRoleHelper::update(array('id' => $cl_role_id,'rights'=> $rights))) { + header('Location: role_edit.php?id='.$role['id']); + exit(); + } else + $err->add($i18n->get('error.db')); + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.edit_role')); +$smarty->assign('content_page_name', 'role_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/roles.php b/roles.php new file mode 100644 index 000000000..569f2649a --- /dev/null +++ b/roles.php @@ -0,0 +1,21 @@ +assign('active_roles', ttTeamHelper::getActiveRolesForUser()); +$smarty->assign('inactive_roles', ttTeamHelper::getInactiveRolesForUser()); +$smarty->assign('title', $i18n->get('title.roles')); +$smarty->assign('content_page_name', 'roles.tpl'); +$smarty->display('index.tpl'); diff --git a/rtl.css b/rtl.css index ede511c8e..890cdd75c 100644 --- a/rtl.css +++ b/rtl.css @@ -2,4 +2,56 @@ html { direction: rtl; -} \ No newline at end of file +} + +.large-screen-label { + text-align: left; +} + +.small-screen-label { + text-align: right; +} + +.td-with-input { + text-align: right; +} + +.checkbox-label { + float: right; +} + +.day-totals-col1 { + text-align: right; +} + +.day-totals-col2 { + text-align: left; +} + +.text-cell { + text-align: right; +} + +.number-cell { + text-align: left; +} + +.time-cell { + text-align: left; +} + +.money-value-cell { + text-align: left; +} + +.note-header-cell { + text-align: left; +} + +.label-cell { + text-align: left; +} + +th.invoice-label { + text-align: left; +} diff --git a/site_map.php b/site_map.php new file mode 100644 index 000000000..f769edd76 --- /dev/null +++ b/site_map.php @@ -0,0 +1,16 @@ +isAuthenticated(); // This call assigns 'authenticated' to smarty. + +$smarty->assign('content_page_name', 'site_map.tpl'); +$smarty->display('index.tpl'); diff --git a/success.php b/success.php new file mode 100644 index 000000000..6fbf8ad2a --- /dev/null +++ b/success.php @@ -0,0 +1,12 @@ +add($i18n->get('msg.success')); +if ($auth->isAuthenticated()) $smarty->assign('authenticated', true); // Used in header.tpl for menu display. + +$smarty->assign('title', $i18n->get('title.success')); +$smarty->assign('content_page_name', 'success.tpl'); +$smarty->display('index.tpl'); diff --git a/swap_roles.php b/swap_roles.php new file mode 100644 index 000000000..7a036a777 --- /dev/null +++ b/swap_roles.php @@ -0,0 +1,53 @@ +isPost()) { + $user_id = (int)$request->getParameter('swap_with'); + $user_details = $user->getUserDetails($user_id); + if (!$user_details) { + header('Location: access_denied.php'); + exit(); + } +} +// End of access checks. + +$form = new Form('swapForm'); +$form->addInput(array('type'=>'combobox','name'=>'swap_with','data'=>$users_for_swap,'datakeys'=>array('id','name'))); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.submit'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); + +if ($request->isPost()) { + if ($request->getParameter('btn_submit')) { + if (ttTeamHelper::swapRolesWith($user_id)) { + header('Location: users.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } + + if ($request->getParameter('btn_cancel')) { + header('Location: users.php'); + exit(); + } +} + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.swapForm.btn_cancel.focus()"'); +$smarty->assign('title', $i18n->get('title.swap_roles')); +$smarty->assign('content_page_name', 'swap_roles.tpl'); +$smarty->display('index.tpl'); diff --git a/task_add.php b/task_add.php index 5ef549bb1..4a3353703 100644 --- a/task_add.php +++ b/task_add.php @@ -1,45 +1,27 @@ tracking_mode) { +// Access checks. +if (!ttAccessAllowed('manage_tasks')) { header('Location: access_denied.php'); exit(); } +if (MODE_PROJECTS_AND_TASKS != $user->getTrackingMode()) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. -$projects = ttTeamHelper::getActiveProjects($user->team_id); +$projects = ttGroupHelper::getActiveProjects(); +$cl_name = $cl_description = ''; if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_description = trim($request->getParameter('description')); @@ -50,20 +32,19 @@ } $form = new Form('taskForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 250px;','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 250px; height: 40px;','value'=>$cl_description)); +$form->addInput(array('type'=>'text','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); $form->addInput(array('type'=>'checkboxgroup','name'=>'projects','layout'=>'H','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_projects)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.add'))); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.add'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description')); + if (!ttValidString($cl_name, false, MAX_NAME_CHARS)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_description, true, MAX_DESCR_CHARS)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); if ($err->no()) { if (!ttTaskHelper::getTaskByName($cl_name)) { if (ttTaskHelper::insert(array( - 'team_id' => $user->team_id, 'name' => $cl_name, 'description' => $cl_description, 'status' => ACTIVE, @@ -71,14 +52,15 @@ header('Location: tasks.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.task_exists')); + $err->add($i18n->get('error.object_exists')); } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('show_projects', count($projects) > 0); $smarty->assign('onload', 'onLoad="document.taskForm.name.focus()"'); -$smarty->assign('title', $i18n->getKey('title.add_task')); +$smarty->assign('title', $i18n->get('title.add_task')); $smarty->assign('content_page_name', 'task_add.tpl'); $smarty->display('index.tpl'); diff --git a/task_delete.php b/task_delete.php index ebc96e219..04008010f 100644 --- a/task_delete.php +++ b/task_delete.php @@ -1,60 +1,45 @@ tracking_mode) { +// Access checks. +if (!ttAccessAllowed('manage_tasks')) { header('Location: access_denied.php'); exit(); } - +if (MODE_PROJECTS_AND_TASKS != $user->getTrackingMode()) { + header('Location: feature_disabled.php'); + exit(); +} $cl_task_id = (int)$request->getParameter('id'); -$task = ttTaskHelper::getTask($cl_task_id); +$task = ttTaskHelper::get($cl_task_id); +if (!$task) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + $task_to_delete = $task['name']; $form = new Form('taskDeleteForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_task_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); if ($request->isPost()) { if ($request->getParameter('btn_delete')) { - if(ttTaskHelper::getTask($cl_task_id)) { + if(ttTaskHelper::get($cl_task_id)) { if (ttTaskHelper::delete($cl_task_id)) { header('Location: tasks.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } elseif ($request->getParameter('btn_cancel')) { header('Location: tasks.php'); exit(); @@ -64,6 +49,6 @@ $smarty->assign('task_to_delete', $task_to_delete); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="document.taskDeleteForm.btn_cancel.focus()"'); -$smarty->assign('title', $i18n->getKey('title.delete_task')); +$smarty->assign('title', $i18n->get('title.delete_task')); $smarty->assign('content_page_name', 'task_delete.tpl'); $smarty->display('index.tpl'); diff --git a/task_edit.php b/task_edit.php index 077c7c6b3..1eb8c7f5b 100644 --- a/task_edit.php +++ b/task_edit.php @@ -1,75 +1,62 @@ tracking_mode) { +// Access checks. +if (!ttAccessAllowed('manage_tasks')) { header('Location: access_denied.php'); exit(); } - +if (MODE_PROJECTS_AND_TASKS != $user->getTrackingMode()) { + header('Location: feature_disabled.php'); + exit(); +} $cl_task_id = (int)$request->getParameter('id'); -$projects = ttTeamHelper::getActiveProjects($user->team_id); +$task = ttTaskHelper::get($cl_task_id); +if (!$task) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$projects = ttGroupHelper::getActiveProjects(); if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_description = trim($request->getParameter('description')); $cl_status = $request->getParameter('status'); $cl_projects = $request->getParameter('projects'); + if ($cl_projects == null) $cl_projects = array(); } else { - $task = ttTaskHelper::getTask($cl_task_id); $cl_name = $task['name']; $cl_description = $task['description']; $cl_status = $task['status']; - $assigned_projects = ttTaskHelper::getAssignedProjects($cl_task_id); + $cl_projects = array(); foreach ($assigned_projects as $project_item) $cl_projects[] = $project_item['id']; } $form = new Form('taskForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_task_id)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 250px;','value'=>$cl_name)); -$form->addInput(array('type'=>'textarea','name'=>'description','style'=>'width: 250px; height: 40px;','value'=>$cl_description)); +$form->addInput(array('type'=>'text','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); $form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status, - 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive')))); + 'data'=>array(ACTIVE=>$i18n->get('dropdown.status_active'),INACTIVE=>$i18n->get('dropdown.status_inactive')))); $form->addInput(array('type'=>'checkboxgroup','name'=>'projects','layout'=>'H','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_projects)); -$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->getKey('button.copy'))); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); +$form->addInput(array('type'=>'submit','name'=>'btn_copy','value'=>$i18n->get('button.copy'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.description')); + if (!ttValidString($cl_name, false, MAX_NAME_CHARS)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_description, true, MAX_DESCR_CHARS)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + if (!ttValidStatus($cl_status)) $err->add($i18n->get('error.field'), $i18n->get('label.status')); if ($err->no()) { if ($request->getParameter('btn_save')) { @@ -85,15 +72,14 @@ header('Location: tasks.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.task_exists')); + $err->add($i18n->get('error.object_exists')); } if ($request->getParameter('btn_copy')) { if (!ttTaskHelper::getTaskByName($cl_name)) { if (ttTaskHelper::insert(array( - 'team_id' => $user->team_id, 'name' => $cl_name, 'description' => $cl_description, 'status' => $cl_status, @@ -101,14 +87,15 @@ header('Location: tasks.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.task_exists')); + $err->add($i18n->get('error.object_exists')); } } } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.edit_task')); +$smarty->assign('show_projects', count($projects) > 0); +$smarty->assign('title', $i18n->get('title.edit_task')); $smarty->assign('content_page_name', 'task_edit.tpl'); $smarty->display('index.tpl'); diff --git a/tasks.php b/tasks.php index 3ea2faaa0..13787a8ab 100644 --- a/tasks.php +++ b/tasks.php @@ -1,43 +1,53 @@ tracking_mode) { +// Access checks. +if (!(ttAccessAllowed('view_own_tasks') || ttAccessAllowed('manage_tasks'))) { header('Location: access_denied.php'); exit(); } +if (MODE_PROJECTS_AND_TASKS != $user->getTrackingMode()) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. + +$config = $user->getConfigHelper(); + +if ($request->isPost()) { + $cl_task_required = $request->getParameter('task_required'); +} else { + $cl_task_required = $config->getDefinedValue('task_required'); +} + +if($user->can('manage_tasks')) { + $active_tasks = ttGroupHelper::getActiveTasks(); + $inactive_tasks = ttGroupHelper::getInactiveTasks(); +} else + $active_tasks = $user->getAssignedTasks(); + +$form = new Form('tasksForm'); +$form->addInput(array('type'=>'checkbox','name'=>'task_required','value'=>$cl_task_required)); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); + +if ($request->isPost()) { + if ($request->getParameter('btn_save')) { + // Save button clicked. Update config. + $config->setDefinedValue('task_required', $cl_task_required); + if (!$user->updateGroup(array('config' => $config->getConfig()))) { + $err->add($i18n->get('error.db')); + } + } +} -$smarty->assign('active_tasks', ttTeamHelper::getActiveTasks($user->team_id)); -$smarty->assign('inactive_tasks', ttTeamHelper::getInactiveTasks($user->team_id)); -$smarty->assign('title', $i18n->getKey('title.tasks')); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('active_tasks', $active_tasks); +$smarty->assign('inactive_tasks', $inactive_tasks); +$smarty->assign('title', $i18n->get('title.tasks')); $smarty->assign('content_page_name', 'tasks.tpl'); $smarty->display('index.tpl'); diff --git a/template_add.php b/template_add.php new file mode 100644 index 000000000..8eef424ef --- /dev/null +++ b/template_add.php @@ -0,0 +1,71 @@ +isPluginEnabled('tp')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. + +$config = new ttConfigHelper($user->getConfig()); +$bindTemplatesWithProjects = $config->getDefinedValue('bind_templates_with_projects'); +$projects = $cl_projects = array(); +if ($bindTemplatesWithProjects) + $projects = ttGroupHelper::getActiveProjects(); + +$cl_name = $cl_description = $cl_content = null; +if ($request->isPost()) { + $cl_name = trim($request->getParameter('name')); + $cl_description = trim($request->getParameter('description')); + $cl_content = trim($request->getParameter('content')); + $cl_projects = $request->getParameter('projects'); +} else { + if ($bindTemplatesWithProjects) { + foreach ($projects as $project_item) + $cl_projects[] = $project_item['id']; + } +} + +$form = new Form('templateForm'); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +$form->addInput(array('type'=>'textarea','name'=>'content','value'=>$cl_content)); +$form->addInput(array('type'=>'checkboxgroup','name'=>'projects','layout'=>'H','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_projects)); +$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); + +if ($request->isPost()) { + // Validate user input. + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + if (!ttValidString($cl_content)) $err->add($i18n->get('error.field'), $i18n->get('label.template')); + if (!ttGroupHelper::validateCheckboxGroupInput($cl_projects, 'tt_projects')) $err->add($i18n->get('error.field'), $i18n->get('label.projects')); + // Finished validating user input. + + if ($err->no()) { + if (ttTemplateHelper::insert(array( + 'name' => $cl_name, + 'description' => $cl_description, + 'content' => $cl_content, + 'projects' => $cl_projects))) { + header('Location: templates.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('show_projects', $bindTemplatesWithProjects && count($projects) > 0); +$smarty->assign('title', $i18n->get('title.add_template')); +$smarty->assign('content_page_name', 'template_add.tpl'); +$smarty->display('index.tpl'); diff --git a/template_delete.php b/template_delete.php new file mode 100644 index 000000000..a4996e05b --- /dev/null +++ b/template_delete.php @@ -0,0 +1,51 @@ +isPluginEnabled('tp')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_template_id = (int)$request->getParameter('id'); +$template = ttTemplateHelper::get($cl_template_id); +if (!$template) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$template_to_delete = $template['name']; + +$form = new Form('templateDeleteForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_template_id)); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); + +if ($request->isPost()) { + if ($request->getParameter('btn_delete')) { + if (ttTemplateHelper::delete($cl_template_id)) { + header('Location: templates.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } elseif ($request->getParameter('btn_cancel')) { + header('Location: templates.php'); + exit(); + } +} // isPost + +$smarty->assign('template_to_delete', $template_to_delete); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.templateDeleteForm.btn_cancel.focus()"'); +$smarty->assign('title', $i18n->get('title.delete_template')); +$smarty->assign('content_page_name', 'template_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/template_edit.php b/template_edit.php new file mode 100644 index 000000000..2ff87b721 --- /dev/null +++ b/template_edit.php @@ -0,0 +1,90 @@ +isPluginEnabled('tp')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_template_id = (int)$request->getParameter('id'); +$template = ttTemplateHelper::get($cl_template_id); +if (!$template) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$config = $user->getConfigHelper(); +$bindTemplatesWithProjects = $config->getDefinedValue('bind_templates_with_projects'); +$projects = $cl_projects = array(); +if ($bindTemplatesWithProjects) + $projects = ttGroupHelper::getActiveProjects(); + +$cl_name = $cl_description = $cl_content = $cl_status = null; +if ($request->isPost()) { + $cl_name = trim($request->getParameter('name')); + $cl_description = trim($request->getParameter('description')); + $cl_content = trim($request->getParameter('content')); + $cl_status = $request->getParameter('status'); + if ($bindTemplatesWithProjects) + $cl_projects = $request->getParameter('projects'); +} else { + $cl_name = $template['name']; + $cl_description = $template['description']; + $cl_content = $template['content']; + $cl_status = $template['status']; + if ($bindTemplatesWithProjects) { + $assigned_projects = ttTemplateHelper::getAssignedProjects($cl_template_id); + foreach ($assigned_projects as $project_item) + $cl_projects[] = $project_item['id']; + } +} + +$form = new Form('templateForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_template_id)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +$form->addInput(array('type'=>'textarea','name'=>'content','value'=>$cl_content)); +$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status, + 'data'=>array(ACTIVE=>$i18n->get('dropdown.status_active'),INACTIVE=>$i18n->get('dropdown.status_inactive')))); +$form->addInput(array('type'=>'checkboxgroup','name'=>'projects','layout'=>'H','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_projects)); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.submit'))); + +if ($request->isPost()) { + // Validate user input. + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + if (!ttValidString($cl_content)) $err->add($i18n->get('error.field'), $i18n->get('label.template')); + if (!ttValidStatus($cl_status)) $err->add($i18n->get('error.field'), $i18n->get('label.status')); + if (!ttGroupHelper::validateCheckboxGroupInput($cl_projects, 'tt_projects')) $err->add($i18n->get('error.field'), $i18n->get('label.projects')); + // Finished validating user input. + + if ($err->no()) { + if (ttTemplateHelper::update(array( + 'id' => $cl_template_id, + 'name' => $cl_name, + 'description' => $cl_description, + 'content' => $cl_content, + 'status' => $cl_status, + 'projects' => $cl_projects))) { + header('Location: templates.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('show_projects', $bindTemplatesWithProjects && count($projects) > 0); +$smarty->assign('title', $i18n->get('title.edit_template')); +$smarty->assign('content_page_name', 'template_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/templates.php b/templates.php new file mode 100644 index 000000000..636d4bc10 --- /dev/null +++ b/templates.php @@ -0,0 +1,63 @@ +isPluginEnabled('tp')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. + +$config = $user->getConfigHelper(); +$trackingMode = $user->getTrackingMode(); +$showBindWithProjectsCheckbox = $trackingMode != MODE_TIME; + +if ($request->isPost()) { + $cl_bind_templates_with_projects = $request->getParameter('bind_templates_with_projects'); + $cl_prepopulate_note = $request->getParameter('prepopulate_note'); +} else { + $cl_bind_templates_with_projects = $config->getDefinedValue('bind_templates_with_projects'); + $cl_prepopulate_note = $config->getDefinedValue('prepopulate_note'); +} + +$form = new Form('templatesForm'); +if ($showBindWithProjectsCheckbox) $form->addInput(array('type'=>'checkbox','name'=>'bind_templates_with_projects','value'=>$cl_bind_templates_with_projects)); +$form->addInput(array('type'=>'checkbox','name'=>'prepopulate_note','value'=>$cl_prepopulate_note)); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); +$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); +$activeTemplates = ttGroupHelper::getActiveTemplates(); +$inactiveTemplates = ttGroupHelper::getInactiveTemplates(); + +if ($request->isPost()) { + if ($request->getParameter('btn_save')) { + // Save button clicked. Update config. + $config->setDefinedValue('bind_templates_with_projects', $cl_bind_templates_with_projects); + $config->setDefinedValue('prepopulate_note', $cl_prepopulate_note); + if (!$user->updateGroup(array('config' => $config->getConfig()))) { + $err->add($i18n->get('error.db')); + } + } + + if ($request->getParameter('btn_add')) { + // The Add button clicked. Redirect to template_add.php page. + header('Location: template_add.php'); + exit(); + } +} + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('active_templates', $activeTemplates); +$smarty->assign('inactive_templates', $inactiveTemplates); +$smarty->assign('show_bind_with_projects_checkbox', $showBindWithProjectsCheckbox); +$smarty->assign('title', $i18n->get('title.templates')); +$smarty->assign('content_page_name', 'templates.tpl'); +$smarty->display('index.tpl'); diff --git a/time.php b/time.php index d8889110a..ee90770b8 100644 --- a/time.php +++ b/time.php @@ -1,152 +1,274 @@ behalf_id && (!$user->can('track_time') || !$user->checkBehalfId())) { + header('Location: access_denied.php'); // Trying on behalf, but no right or wrong user. + exit(); +} +if (!$user->behalf_id && !$user->can('track_own_time') && !$user->adjustBehalfId()) { + header('Location: access_denied.php'); // Trying as self, but no right for self, and noone to work on behalf. + exit(); +} +if ($request->isPost()) { + $userChanged = (bool)$request->getParameter('user_changed'); // Reused in multiple places below. + if ($userChanged && !($user->can('track_time') && $user->isUserValid((int)$request->getParameter('user')))) { + header('Location: access_denied.php'); // User changed, but no right or wrong user id. + exit(); + } +} +// If we are passed in a date, make sure it is in correct format. +$date = $request->getParameter('date'); +if ($date && !ttValidDbDateFormatDate($date)) { header('Location: access_denied.php'); exit(); } +if ($request->isPost()) { + // Validate that browser_today parameter is in correct format. + $browser_today = $request->getParameter('browser_today'); + if ($browser_today && !ttValidDbDateFormatDate($browser_today)) { + header('Location: access_denied.php'); + exit(); + } +} +// Additional checks for hidden controls used for completion of uncompleted records. +// browser_date, browser_time, record_id. +if ($request->isPost() && $request->getParameter('btn_stop')) { + $recordId = (int) $request->getParameter('record_id'); + $time_rec = ttTimeHelper::getRecord($recordId); + if (!$time_rec) { + // We are passed a bogus record id. + header('Location: access_denied.php'); + exit(); + } + + // Validate that browser_date parameter is in correct format. + $browser_date = $request->getParameter('browser_date'); + if ($browser_date && !ttValidDbDateFormatDate($browser_date)) { + header('Location: access_denied.php'); + exit(); + } + // Validate that browser_time parameter is in correct format. + $browser_time = $request->getParameter('browser_time'); + if ($browser_time && !ttValidTime($browser_time)) { + header('Location: access_denied.php'); + exit(); + } +} +// End of access checks. + +// Determine user for whom we display this page. +if ($request->isPost() && $userChanged) { + $user_id = (int)$request->getParameter('user'); + $user->setOnBehalfUser($user_id); +} else { + $user_id = $user->getUser(); +} + +$group_id = $user->getGroup(); +$config = new ttConfigHelper($user->getConfig()); + +$showClient = $user->isPluginEnabled('cl'); +$showBillable = $user->isPluginEnabled('iv'); +$trackingMode = $user->getTrackingMode(); +$showProject = MODE_PROJECTS == $trackingMode || MODE_PROJECTS_AND_TASKS == $trackingMode; +$showTask = MODE_PROJECTS_AND_TASKS == $trackingMode; +$taskRequired = false; +if ($showTask) $taskRequired = $config->getDefinedValue('task_required'); +$recordType = $user->getRecordType(); +$showStart = TYPE_START_FINISH == $recordType || TYPE_ALL == $recordType; +$showDuration = TYPE_DURATION == $recordType || TYPE_ALL == $recordType; +$showFiles = $user->isPluginEnabled('at'); +$showRecordCustomFields = $user->isOptionEnabled('record_custom_fields'); +$oneUncompleted = $config->getDefinedValue('one_uncompleted'); // Initialize and store date in session. $cl_date = $request->getParameter('date', @$_SESSION['date']); -$selected_date = new DateAndTime(DB_DATEFORMAT, $cl_date); -if($selected_date->isError()) - $selected_date = new DateAndTime(DB_DATEFORMAT); +$selected_date = new ttDate($cl_date); if(!$cl_date) - $cl_date = $selected_date->toString(DB_DATEFORMAT); + $cl_date = $selected_date->toString(); $_SESSION['date'] = $cl_date; // Use custom fields plugin if it is enabled. if ($user->isPluginEnabled('cf')) { require_once('plugins/CustomFields.class.php'); - $custom_fields = new CustomFields($user->team_id); + $custom_fields = new CustomFields(); $smarty->assign('custom_fields', $custom_fields); } +$showNoteColumn = !$config->getDefinedValue('time_note_on_separate_row'); +$showNoteRow = $config->getDefinedValue('time_note_on_separate_row'); +if ($showNoteRow) { + // Determine column span for note field. + $colspan = 0; + if ($showClient) $colspan++; + if ($showRecordCustomFields && isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $colspan++; + } + } + if ($showProject) $colspan++; + if ($showTask) $colspan++; + if ($showStart) $colspan += 2; // Another for show finish. + $colspan++; // There is always a duration. + if ($showFiles) $colspan++; + $colspan++; // There is always an edit column. + // $colspan++; // There is always a delete column. + // $colspan--; // Remove one column for label. + $smarty->assign('colspan', $colspan); +} + if ($user->isPluginEnabled('mq')){ require_once('plugins/MonthlyQuota.class.php'); $quota = new MonthlyQuota(); - $month_quota = $quota->get($selected_date->mYear, $selected_date->mMonth); - $month_total = ttTimeHelper::getTimeForMonth($user->getActiveUser(), $selected_date); - $minutes_left = ttTimeHelper::toMinutes($month_quota) - ttTimeHelper::toMinutes($month_total); - + $month_quota_minutes = $quota->getUserQuota($selected_date->year, $selected_date->month); + $quota_minutes_from_1st = $quota->getUserQuotaFrom1st($selected_date); + $month_total = ttTimeHelper::getTimeForMonth($selected_date); + $month_total_minutes = ttTimeHelper::toMinutes($month_total); + $balance_left = $quota_minutes_from_1st - $month_total_minutes; + $minutes_left = $month_quota_minutes - $month_total_minutes; + $smarty->assign('month_total', $month_total); + $smarty->assign('month_quota', ttTimeHelper::toAbsDuration($month_quota_minutes)); + $smarty->assign('over_balance', $balance_left < 0); + $smarty->assign('balance_remaining', ttTimeHelper::toAbsDuration($balance_left)); $smarty->assign('over_quota', $minutes_left < 0); $smarty->assign('quota_remaining', ttTimeHelper::toAbsDuration($minutes_left)); } // Initialize variables. -$cl_start = trim($request->getParameter('start')); -$cl_finish = trim($request->getParameter('finish')); -$cl_duration = trim($request->getParameter('duration')); -$cl_note = trim($request->getParameter('note')); -// Custom field. -$cl_cf_1 = trim($request->getParameter('cf_1', ($request->getMethod()=='POST'? null : @$_SESSION['cf_1']))); -$_SESSION['cf_1'] = $cl_cf_1; +$cl_start = is_null($request->getParameter('start')) ? null : trim($request->getParameter('start')); +$cl_finish = is_null($request->getParameter('finish')) ? null : trim($request->getParameter('finish')); +$cl_duration = is_null($request->getParameter('duration')) ? null : trim($request->getParameter('duration')); +$cl_note = is_null($request->getParameter('note')) ? null : trim($request->getParameter('note')); $cl_billable = 1; -if ($user->isPluginEnabled('iv')) { +if ($showBillable) { if ($request->isPost()) { $cl_billable = $request->getParameter('billable'); $_SESSION['billable'] = (int) $cl_billable; - } else + } else if (isset($_SESSION['billable'])) $cl_billable = $_SESSION['billable']; } -$on_behalf_id = $request->getParameter('onBehalfUser', (isset($_SESSION['behalf_id'])? $_SESSION['behalf_id'] : $user->id)); -$cl_client = $request->getParameter('client', ($request->getMethod()=='POST'? null : @$_SESSION['client'])); +$cl_client = $request->getParameter('client', ($request->isPost() ? null : @$_SESSION['client'])); $_SESSION['client'] = $cl_client; -$cl_project = $request->getParameter('project', ($request->getMethod()=='POST'? null : @$_SESSION['project'])); +$cl_project = $request->getParameter('project', ($request->isPost() ? null : @$_SESSION['project'])); $_SESSION['project'] = $cl_project; -$cl_task = $request->getParameter('task', ($request->getMethod()=='POST'? null : @$_SESSION['task'])); +$cl_task = $request->getParameter('task', ($request->isPost() ? null : @$_SESSION['task'])); $_SESSION['task'] = $cl_task; +// Handle time custom fields. +$timeCustomFields = array(); +if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $control_name = 'time_field_'.$timeField['id']; + $cl_control_name = $request->getParameter($control_name, ($request->isPost() ? null : @$_SESSION[$control_name])); + $_SESSION[$control_name] = $cl_control_name; + $timeCustomFields[$timeField['id']] = array('field_id' => $timeField['id'], + 'control_name' => $control_name, + 'label' => $timeField['label'], + 'type' => $timeField['type'], + 'required' => $timeField['required'], + 'value' => is_null($cl_control_name) ? null : trim($cl_control_name)); + } +} + // Elements of timeRecordForm. $form = new Form('timeRecordForm'); +$largeScreenCalendarRowSpan = 1; // Number of rows calendar spans on large screens. -if ($user->canManageTeam()) { - $user_list = ttTeamHelper::getActiveUsers(array('putSelfFirst'=>true)); - if (count($user_list) > 1) { +// Dropdown for user and a hidden control to indicate user change. +if ($user->can('track_time')) { + $rank = $user->getMaxRankForGroup($group_id); + if ($user->can('track_own_time')) + $options = array('status'=>ACTIVE,'max_rank'=>$rank,'include_self'=>true,'self_first'=>true); + else + $options = array('status'=>ACTIVE,'max_rank'=>$rank); + $user_list = $user->getUsers($options); + if (count($user_list) >= 1) { $form->addInput(array('type'=>'combobox', - 'onchange'=>'this.form.submit();', - 'name'=>'onBehalfUser', - 'style'=>'width: 250px;', - 'value'=>$on_behalf_id, + 'onchange'=>'document.timeRecordForm.user_changed.value=1;document.timeRecordForm.submit();', + 'name'=>'user', + 'value'=>$user_id, 'data'=>$user_list, 'datakeys'=>array('id','name'))); - $smarty->assign('on_behalf_control', 1); + $form->addInput(array('type'=>'hidden','name'=>'user_changed')); + $largeScreenCalendarRowSpan += 2; + $smarty->assign('user_dropdown', 1); } } // Dropdown for clients in MODE_TIME. Use all active clients. -if (MODE_TIME == $user->tracking_mode && $user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); +// Note: for other tracking modes the control is added further below. +if (MODE_TIME == $trackingMode && $showClient) { + $active_clients = ttGroupHelper::getActiveClients(true); $form->addInput(array('type'=>'combobox', 'onchange'=>'fillProjectDropdown(this.value);', 'name'=>'client', - 'style'=>'width: 250px;', 'value'=>$cl_client, 'data'=>$active_clients, 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; // Note: in other modes the client list is filtered to relevant clients only. See below. } -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { +// Billable checkbox. +if ($showBillable) { + $form->addInput(array('type'=>'checkbox','name'=>'billable','value'=>$cl_billable)); + $largeScreenCalendarRowSpan += 2; +} + +// If we have time custom fields - add controls for them. +if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + if ($timeField['type'] == CustomFields::TYPE_TEXT) { + $form->addInput(array('type'=>'text','name'=>$field_name,'value'=>$timeCustomFields[$timeField['id']]['value'])); + } elseif ($timeField['type'] == CustomFields::TYPE_DROPDOWN) { + $form->addInput(array('type'=>'combobox','name'=>$field_name, + 'data'=>CustomFields::getOptions($timeField['id']), + 'value'=>$timeCustomFields[$timeField['id']]['value'], + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + } + $largeScreenCalendarRowSpan += 2; + } +} + +// If we show project dropdown, add controls for project and client. +$project_list = $client_list = array(); +if ($showProject) { // Dropdown for projects assigned to user. - $project_list = $user->getAssignedProjects(); + $options['include_templates'] = $user->isPluginEnabled('tp') && $config->getDefinedValue('bind_templates_with_projects'); + $project_list = $user->getAssignedProjects($options); $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillTaskDropdown(this.value);', + 'onchange'=>'fillTaskDropdown(this.value);fillTemplateDropdown(this.value);prepopulateNote();', 'name'=>'project', - 'style'=>'width: 250px;', 'value'=>$cl_project, 'data'=>$project_list, 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; - // Dropdown for clients if the clients plugin is enabled. - if ($user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - // We need an array of assigned project ids to do some trimming. + // Client dropdown. + if ($showClient) { + $active_clients = ttGroupHelper::getActiveClients(true); + // We need an array of assigned project ids to do some trimming. foreach($project_list as $project) $projects_assigned_to_user[] = $project['id']; @@ -164,126 +286,163 @@ $form->addInput(array('type'=>'combobox', 'onchange'=>'fillProjectDropdown(this.value);', 'name'=>'client', - 'style'=>'width: 250px;', 'value'=>$cl_client, 'data'=>$client_list, 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; } } -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - $task_list = ttTeamHelper::getActiveTasks($user->team_id); +// Task dropdown. +$task_list = array(); +if ($showTask) { + $task_list = ttGroupHelper::getActiveTasks(); $form->addInput(array('type'=>'combobox', 'name'=>'task', - 'style'=>'width: 250px;', 'value'=>$cl_task, 'data'=>$task_list, 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; } -// Add other controls. -if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) { +// Start and finish controls. +if ($showStart) { $form->addInput(array('type'=>'text','name'=>'start','value'=>$cl_start,'onchange'=>"formDisable('start');")); $form->addInput(array('type'=>'text','name'=>'finish','value'=>$cl_finish,'onchange'=>"formDisable('finish');")); + if ($user->punch_mode && !$user->canOverridePunchMode()) { + // Make the start and finish fields read-only. + $form->getElement('start')->setEnabled(false); + $form->getElement('finish')->setEnabled(false); + } + $largeScreenCalendarRowSpan += 4; } -if (!$user->canManageTeam() && defined('READONLY_START_FINISH') && isTrue(READONLY_START_FINISH)) { - // Make the start and finish fields read-only. - $form->getElement('start')->setEnable(false); - $form->getElement('finish')->setEnable(false); + +// Duration control. +if ($showDuration) { + $placeholder = $user->getDecimalMark() == ',' ? str_replace('.', ',', $i18n->get('form.time.duration_placeholder')) : $i18n->get('form.time.duration_placeholder'); + $form->addInput(array('type'=>'text','name'=>'duration','placeholder'=>$placeholder,'value'=>$cl_duration,'onchange'=>"formDisable('duration');")); + $largeScreenCalendarRowSpan += 2; } -if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type)) - $form->addInput(array('type'=>'text','name'=>'duration','value'=>$cl_duration,'onchange'=>"formDisable('duration');")); -if (!defined('NOTE_INPUT_HEIGHT')) - define('NOTE_INPUT_HEIGHT', 40); -$form->addInput(array('type'=>'textarea','name'=>'note','style'=>'width: 600px; height:'.NOTE_INPUT_HEIGHT.'px;','value'=>$cl_note)); -$form->addInput(array('type'=>'calendar','name'=>'date','value'=>$cl_date)); // calendar -if ($user->isPluginEnabled('iv')) - $form->addInput(array('type'=>'checkbox','name'=>'billable','data'=>1,'value'=>$cl_billable)); -$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_submit click. -$form->addInput(array('type'=>'submit','name'=>'btn_submit','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.submit'))); - -// If we have custom fields - add controls for them. -if ($custom_fields && $custom_fields->fields[0]) { - // Only one custom field is supported at this time. - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) { - $form->addInput(array('type'=>'text','name'=>'cf_1','value'=>$cl_cf_1)); - } elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) { - $form->addInput(array('type'=>'combobox','name'=>'cf_1', - 'style'=>'width: 250px;', - 'value'=>$cl_cf_1, - 'data'=>$custom_fields->options, - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + +// File upload control. +if ($showFiles) { + $form->addInput(array('type'=>'upload','name'=>'newfile','value'=>$i18n->get('button.submit'))); + $largeScreenCalendarRowSpan += 2; +} + +// If we have templates, add a dropdown to select one. +if ($user->isPluginEnabled('tp')){ + $template_list = ttGroupHelper::getActiveTemplates(); + if (count($template_list) >= 1) { + $form->addInput(array('type'=>'combobox', + 'onchange'=>'fillNote(this.value);', + 'name'=>'template', + 'data'=>$template_list, + 'datakeys'=>array('id','name'), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $smarty->assign('template_dropdown', 1); + $smarty->assign('bind_templates_with_projects', $config->getDefinedValue('bind_templates_with_projects')); + $smarty->assign('prepopulate_note', $config->getDefinedValue('prepopulate_note')); + $smarty->assign('template_list', $template_list); + $largeScreenCalendarRowSpan += 2; } } -// Submit. +// Note control. +$form->addInput(array('type'=>'textarea','name'=>'note','value'=>$cl_note)); + +// Calendar. +$form->addInput(array('type'=>'calendar','name'=>'date','value'=>$cl_date)); // calendar + +// A hidden control for today's date from user's browser. +$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_submit click. + +// Submit button. +$form->addInput(array('type'=>'submit','name'=>'btn_submit','onclick'=>'browser_today.value=get_date()','value'=>$i18n->get('button.submit'))); + if ($request->isPost()) { if ($request->getParameter('btn_submit')) { + // Submit button clicked. + + // Use the "limit" plugin if we have one. Ignore include errors. + // The "limit" plugin is not required for normal operation of Time Tracker. + @include('plugins/limit/access_check.php'); // Validate user input. - if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client) - $err->add($i18n->getKey('error.client')); - if ($custom_fields) { - if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $err->add($i18n->getKey('error.field'), $custom_fields->fields[0]['label']); + if ($showClient && $user->isOptionEnabled('client_required') && !$cl_client) + $err->add($i18n->get('error.client')); + // Validate input in time custom fields. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($timeCustomFields as $timeField) { + // Validation is the same for text and dropdown fields. + if (!ttValidString($timeField['value'], !$timeField['required'])) $err->add($i18n->get('error.field'), htmlspecialchars($timeField['label'])); + } + } + if ($showProject) { + if (!$cl_project) $err->add($i18n->get('error.project')); } - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $err->add($i18n->getKey('error.project')); + if ($showTask && $taskRequired) { + if (!$cl_task) $err->add($i18n->get('error.task')); } - if (strlen($cl_duration) == 0) { + if ($cl_duration == null) { if ($cl_start || $cl_finish) { if (!ttTimeHelper::isValidTime($cl_start)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.start')); + $err->add($i18n->get('error.field'), $i18n->get('label.start')); if ($cl_finish) { if (!ttTimeHelper::isValidTime($cl_finish)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.finish')); + $err->add($i18n->get('error.field'), $i18n->get('label.finish')); if (!ttTimeHelper::isValidInterval($cl_start, $cl_finish)) - $err->add($i18n->getKey('error.interval'), $i18n->getKey('label.finish'), $i18n->getKey('label.start')); + $err->add($i18n->get('error.interval'), $i18n->get('label.finish'), $i18n->get('label.start')); } } else { - if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) { - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.start')); - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish')); + if ($showStart) { + $err->add($i18n->get('error.empty'), $i18n->get('label.start')); + $err->add($i18n->get('error.empty'), $i18n->get('label.finish')); } - if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type)) - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration')); + if ($showDuration) + $err->add($i18n->get('error.empty'), $i18n->get('label.duration')); } } else { - if (!ttTimeHelper::isValidDuration($cl_duration)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); + if (false === ttTimeHelper::postedDurationToMinutes($cl_duration)) + $err->add($i18n->get('error.field'), $i18n->get('label.duration')); + } + if (!ttValidString($cl_note, true)) $err->add($i18n->get('error.field'), $i18n->get('label.note')); + if ($user->isPluginEnabled('tp') && !ttValidTemplateText($cl_note)) { + $err->add($i18n->get('error.field'), $i18n->get('label.note')); } - if (!ttValidString($cl_note, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.note')); // Finished validating user input. // Prohibit creating entries in future. - if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($selected_date->after($browser_today)) - $err->add($i18n->getKey('error.future_date')); + if (!$user->isOptionEnabled('future_entries')) { + $browser_today = new ttDate($request->getParameter('browser_today', null)); + $server_tomorrow = new ttDate(); + $server_tomorrow->incrementDay(); + if ($selected_date->after($browser_today) || $selected_date->after($server_tomorrow)) + $err->add($i18n->get('error.future_date')); } // Prohibit creating entries in locked range. if ($user->isDateLocked($selected_date)) - $err->add($i18n->getKey('error.range_locked')); + $err->add($i18n->get('error.range_locked')); // Prohibit creating another uncompleted record. - if ($err->no()) { - if (($not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser())) && (($cl_finish == '') && ($cl_duration == ''))) - $err->add($i18n->getKey('error.uncompleted_exists')." ".$i18n->getKey('error.goto_uncompleted').""); + if ($err->no() && $oneUncompleted) { + if (($not_completed_rec = ttTimeHelper::getUncompleted($user_id)) && (($cl_finish == '') && ($cl_duration == ''))) + $err->add($i18n->get('error.uncompleted_exists')." ".$i18n->get('error.goto_uncompleted').""); } // Prohibit creating an overlapping record. if ($err->no()) { - if (ttTimeHelper::overlaps($user->getActiveUser(), $cl_date, $cl_start, $cl_finish)) - $err->add($i18n->getKey('error.overlap')); + if (ttTimeHelper::overlaps($user_id, $cl_date, $cl_start, $cl_finish)) + $err->add($i18n->get('error.overlap')); } // Insert record. if ($err->no()) { $id = ttTimeHelper::insert(array( 'date' => $cl_date, - 'user_id' => $user->getActiveUser(), 'client' => $cl_client, 'project' => $cl_project, 'task' => $cl_task, @@ -293,35 +452,41 @@ 'note' => $cl_note, 'billable' => $cl_billable)); - // Insert a custom field if we have it. + // Insert time custom fields if we have them. $result = true; - if ($id && $custom_fields && $cl_cf_1) { - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) - $result = $custom_fields->insert($id, $custom_fields->fields[0]['id'], null, $cl_cf_1); - elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) - $result = $custom_fields->insert($id, $custom_fields->fields[0]['id'], $cl_cf_1, null); + if ($id && isset($custom_fields) && $custom_fields->timeFields) { + $result = $custom_fields->insertTimeFields($id, $timeCustomFields); } - if ($id && $result) { + + // Put a new file in storage if we have it. + if ($id && $showFiles && $_FILES['newfile']['name']) { + $fileHelper = new ttFileHelper($err); + $fields = array('entity_type'=>'time', + 'entity_id' => $id, + 'file_name' => $_FILES['newfile']['name']); + $fileHelper->putFile($fields); + } + + if ($id && $result && $err->no()) { header('Location: time.php'); exit(); } - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } elseif ($request->getParameter('btn_stop')) { // Stop button pressed to finish an uncompleted record. $record_id = $request->getParameter('record_id'); - $record = ttTimeHelper::getRecord($record_id, $user->getActiveUser()); + $record = ttTimeHelper::getRecord($record_id); $browser_date = $request->getParameter('browser_date'); $browser_time = $request->getParameter('browser_time'); // Can we complete this record? if ($record['date'] == $browser_date // closing today's record && ttTimeHelper::isValidInterval($record['start'], $browser_time) // finish time is greater than start time - && !ttTimeHelper::overlaps($user->getActiveUser(), $browser_date, $record['start'], $browser_time)) { // no overlap + && !ttTimeHelper::overlaps($user_id, $browser_date, $record['start'], $browser_time)) { // no overlap $res = ttTimeHelper::update(array( 'id'=>$record['id'], 'date'=>$record['date'], - 'user_id'=>$user->getActiveUser(), 'client'=>$record['client_id'], 'project'=>$record['project_id'], 'task'=>$record['task_id'], @@ -330,39 +495,44 @@ 'note'=>$record['comment'], 'billable'=>$record['billable'])); if (!$res) - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else { // Cannot complete, redirect for manual edit. header('Location: time_edit.php?id='.$record_id); exit(); } } - elseif ($request->getParameter('onBehalfUser')) { - if($user->canManageTeam()) { - unset($_SESSION['behalf_id']); - unset($_SESSION['behalf_name']); - - if($on_behalf_id != $user->id) { - $_SESSION['behalf_id'] = $on_behalf_id; - $_SESSION['behalf_name'] = ttUserHelper::getUserName($on_behalf_id); - } - header('Location: time.php'); - exit(); - } - } } // isPost -$week_total = ttTimeHelper::getTimeForWeek($user->getActiveUser(), $selected_date); +$week_total = ttTimeHelper::getTimeForWeek($selected_date); +$timeRecords = ttTimeHelper::getRecords($cl_date, $showFiles); +$showNavigation = ($user->isPluginEnabled('wv') && !$user->isOptionEnabled('week_menu')) || + ($user->isPluginEnabled('pu') && !$user->isOptionEnabled('puncher_menu')); +$smarty->assign('large_screen_calendar_row_span', $largeScreenCalendarRowSpan); +$smarty->assign('selected_date', $selected_date); $smarty->assign('week_total', $week_total); -$smarty->assign('day_total', ttTimeHelper::getTimeForDay($user->getActiveUser(), $cl_date)); -$smarty->assign('time_records', ttTimeHelper::getRecords($user->getActiveUser(), $cl_date)); +$smarty->assign('day_total', ttTimeHelper::getTimeForDay($cl_date)); +$smarty->assign('time_records', $timeRecords); +$smarty->assign('show_record_custom_fields', $showRecordCustomFields); +$smarty->assign('show_navigation', $showNavigation); +$smarty->assign('show_client', $showClient); +$smarty->assign('show_billable', $showBillable); +$smarty->assign('show_project', $showProject); +$smarty->assign('show_task', $showTask); +$smarty->assign('task_required', $taskRequired); +$smarty->assign('show_start', $showStart); +$smarty->assign('show_duration', $showDuration); +$smarty->assign('show_note_column', $showNoteColumn); +$smarty->assign('show_note_row', $showNoteRow); +$smarty->assign('show_files', $showFiles); $smarty->assign('client_list', $client_list); $smarty->assign('project_list', $project_list); $smarty->assign('task_list', $task_list); +$smarty->assign('one_uncompleted', $oneUncompleted); $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="fillDropdowns()"'); -$smarty->assign('timestring', $selected_date->toString($user->date_format)); -$smarty->assign('title', $i18n->getKey('title.time')); +$smarty->assign('onload', 'onLoad="fillDropdowns();prepopulateNote();adjustTodayLinks()"'); +$smarty->assign('timestring', $selected_date->toString($user->getDateFormat())); +$smarty->assign('title', $i18n->get('title.time')); $smarty->assign('content_page_name', 'time.tpl'); $smarty->display('index.tpl'); diff --git a/time_delete.php b/time_delete.php index c50172849..cd0af11ab 100644 --- a/time_delete.php +++ b/time_delete.php @@ -1,74 +1,53 @@ getParameter('id'); +$time_rec = ttTimeHelper::getRecord($cl_id); +if (!$time_rec || $time_rec['approved'] || $time_rec['timesheet_id'] || $time_rec['invoice_id']) { + // Prohibit deleting not ours, approved, assigned to timesheet, or invoiced records. + header('Location: access_denied.php'); + exit(); +} +// End of access checks. -$cl_id = $request->getParameter('id'); -$time_rec = ttTimeHelper::getRecord($cl_id, $user->getActiveUser()); - -// Prohibit deleting invoiced records. -if ($time_rec['invoice_id']) die($i18n->getKey('error.sys')); - -// Escape comment for presentation. -$time_rec['comment'] = htmlspecialchars($time_rec['comment']); +$trackingMode = $user->getTrackingMode(); +$showProject = MODE_PROJECTS == $trackingMode || MODE_PROJECTS_AND_TASKS == $trackingMode; +$showTask = MODE_PROJECTS_AND_TASKS == $trackingMode; +$recordType = $user->getRecordType(); +$showStart = TYPE_START_FINISH == $recordType || TYPE_ALL == $recordType; +$showDuration = TYPE_DURATION == $recordType || TYPE_ALL == $recordType; if ($request->isPost()) { if ($request->getParameter('delete_button')) { // Delete button pressed. // Determine if it's okay to delete the record. - $item_date = new DateAndTime(DB_DATEFORMAT, $time_rec['date']); + $item_date = new ttDate($time_rec['date']); // Determine if the record is uncompleted. $uncompleted = ($time_rec['duration'] == '0:00'); if ($user->isDateLocked($item_date) && !$uncompleted) - $err->add($i18n->getKey('error.range_locked')); + $err->add($i18n->get('error.range_locked')); if ($err->no()) { - // Delete the record. - $result = ttTimeHelper::delete($cl_id, $user->getActiveUser()); - - if ($result) { + if (ttTimeHelper::delete($cl_id)) { header('Location: time.php'); exit(); } else { - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } } @@ -80,11 +59,15 @@ $form = new Form('timeRecordForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); -$form->addInput(array('type'=>'submit','name'=>'delete_button','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'cancel_button','value'=>$i18n->getKey('button.cancel'))); +$form->addInput(array('type'=>'submit','name'=>'delete_button','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'cancel_button','value'=>$i18n->get('button.cancel'))); $smarty->assign('time_rec', $time_rec); +$smarty->assign('show_project', $showProject); +$smarty->assign('show_task', $showTask); +$smarty->assign('show_start', $showStart); +$smarty->assign('show_duration', $showDuration); $smarty->assign('forms', array($form->getName() => $form->toArray())); -$smarty->assign('title', $i18n->getKey('title.delete_time_record')); +$smarty->assign('title', $i18n->get('title.delete_time_record')); $smarty->assign('content_page_name', 'time_delete.tpl'); $smarty->display('index.tpl'); diff --git a/time_edit.php b/time_edit.php index aa204198b..35037dcaf 100644 --- a/time_edit.php +++ b/time_edit.php @@ -1,77 +1,94 @@ getParameter('id'); +$time_rec = ttTimeHelper::getRecord($cl_id); +if (!$time_rec) $time_rec = ttTimeHelper::getOnBehalfRecord($cl_id); +if (!$time_rec || $time_rec['approved'] || $time_rec['timesheet_id'] || $time_rec['invoice_id']) { + // Prohibit editing not ours, approved, assigned to timesheet, or invoiced records. + header('Location: access_denied.php'); + exit(); +} +if ($request->isPost()) { + // Validate that browser_today parameter is in correct format. + $browser_today = $request->getParameter('browser_today'); + if ($browser_today && !ttValidDbDateFormatDate($browser_today)) { + header('Location: access_denied.php'); + exit(); + } +} +// End of access checks. + +$user_id = $user->getUser(); +$config = new ttConfigHelper($user->getConfig()); + +$showClient = $user->isPluginEnabled('cl'); +$showBillable = $user->isPluginEnabled('iv'); +$showPaidStatus = $user->isPluginEnabled('ps') && $user->can('manage_invoices'); +$trackingMode = $user->getTrackingMode(); +$showProject = MODE_PROJECTS == $trackingMode || MODE_PROJECTS_AND_TASKS == $trackingMode; +$showTask = MODE_PROJECTS_AND_TASKS == $trackingMode; +$taskRequired = false; +if ($showTask) $taskRequired = $config->getDefinedValue('task_required'); +$recordType = $user->getRecordType(); +$showStart = TYPE_START_FINISH == $recordType || TYPE_ALL == $recordType; +$showDuration = TYPE_DURATION == $recordType || TYPE_ALL == $recordType; +$oneUncompleted = $config->getDefinedValue('one_uncompleted'); // Use custom fields plugin if it is enabled. if ($user->isPluginEnabled('cf')) { require_once('plugins/CustomFields.class.php'); - $custom_fields = new CustomFields($user->team_id); + $custom_fields = new CustomFields(); $smarty->assign('custom_fields', $custom_fields); } -$cl_id = $request->getParameter('id'); - -// Get the time record we are editing. -$time_rec = ttTimeHelper::getRecord($cl_id, $user->getActiveUser()); - -// Prohibit editing invoiced records. -if ($time_rec['invoice_id']) die($i18n->getKey('error.sys')); - -$item_date = new DateAndTime(DB_DATEFORMAT, $time_rec['date']); +$item_date = new ttDate($time_rec['date']); +$confirm_save = $user->getConfigOption('confirm_save'); // Initialize variables. $cl_start = $cl_finish = $cl_duration = $cl_date = $cl_note = $cl_project = $cl_task = $cl_billable = null; if ($request->isPost()) { - $cl_start = trim($request->getParameter('start')); - $cl_finish = trim($request->getParameter('finish')); - $cl_duration = trim($request->getParameter('duration')); + $cl_start = is_null($request->getParameter('start')) ? null : trim($request->getParameter('start')); + $cl_finish = is_null($request->getParameter('finish')) ? null : trim($request->getParameter('finish')); + $cl_duration = is_null($request->getParameter('duration')) ? null : trim($request->getParameter('duration')); $cl_date = $request->getParameter('date'); $cl_note = trim($request->getParameter('note')); - $cl_cf_1 = trim($request->getParameter('cf_1')); + // If we have time custom fields - collect input. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $control_name = 'time_field_'.$timeField['id']; + $timeCustomFields[$timeField['id']] = array('field_id' => $timeField['id'], + 'control_name' => $control_name, + 'label' => $timeField['label'], + 'type' => $timeField['type'], + 'required' => $timeField['required'], + 'value' => trim($request->getParameter($control_name))); + } + } $cl_client = $request->getParameter('client'); $cl_project = $request->getParameter('project'); $cl_task = $request->getParameter('task'); $cl_billable = 1; - if ($user->isPluginEnabled('iv')) + if ($showBillable) $cl_billable = $request->getParameter('billable'); + $cl_paid = 0; + if ($showPaidStatus) + $cl_paid = $request->getParameter('paid'); } else { $cl_client = $time_rec['client_id']; $cl_project = $time_rec['project_id']; @@ -79,26 +96,30 @@ $cl_start = $time_rec['start']; $cl_finish = $time_rec['finish']; $cl_duration = $time_rec['duration']; - $cl_date = $item_date->toString($user->date_format); + $cl_date = $item_date->toString($user->getDateFormat()); $cl_note = $time_rec['comment']; - // If we have custom fields - obtain values for them. - if ($custom_fields) { - // Get custom field value for time record. - $fields = $custom_fields->get($time_rec['id']); - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) - $cl_cf_1 = $fields[0]['value']; - elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) - $cl_cf_1 = $fields[0]['option_id']; + // If we have time custom fields - collect values from database. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $control_name = 'time_field_'.$timeField['id']; + $timeCustomFields[$timeField['id']] = array('field_id' => $timeField['id'], + 'control_name' => $control_name, + 'label' => $timeField['label'], + 'type' => $timeField['type'], + 'required' => $timeField['required'], + 'value' => $custom_fields->getTimeFieldValue($cl_id, $timeField['id'], $timeField['type'])); + } } $cl_billable = $time_rec['billable']; + $cl_paid = $time_rec['paid']; // Add an info message to the form if we are editing an uncompleted record. if (strlen($cl_start) > 0 && $cl_start == $cl_finish && $cl_duration == '0:00') { - $cl_finish = ''; - $cl_duration = ''; - $msg->add($i18n->getKey('form.time_edit.uncompleted')); + $cl_finish = null; + $cl_duration = null; + $msg->add($i18n->get('form.time_edit.uncompleted')); } } @@ -106,34 +127,59 @@ $form = new Form('timeRecordForm'); // Dropdown for clients in MODE_TIME. Use all active clients. -if (MODE_TIME == $user->tracking_mode && $user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); +// Note: for other tracking modes the control is added further below. +if (MODE_TIME == $trackingMode && $showClient) { + $active_clients = ttGroupHelper::getActiveClients(true); $form->addInput(array('type'=>'combobox', 'onchange'=>'fillProjectDropdown(this.value);', 'name'=>'client', - 'style'=>'width: 250px;', 'value'=>$cl_client, 'data'=>$active_clients, 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); // Note: in other modes the client list is filtered to relevant clients only. See below. } -if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { +// Billable checkbox. +if ($showBillable) + $form->addInput(array('type'=>'checkbox','name'=>'billable','value'=>$cl_billable)); + +// Paid status checkbox. +if ($showPaidStatus) + $form->addInput(array('type'=>'checkbox','name'=>'paid','value'=>$cl_paid)); + +// If we have time custom fields - add controls for them. +if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + if ($timeField['type'] == CustomFields::TYPE_TEXT) { + $form->addInput(array('type'=>'text','name'=>$field_name,'value'=>$timeCustomFields[$timeField['id']]['value'])); + } elseif ($timeField['type'] == CustomFields::TYPE_DROPDOWN) { + $form->addInput(array('type'=>'combobox','name'=>$field_name, + 'data'=>CustomFields::getOptions($timeField['id']), + 'value'=>$timeCustomFields[$timeField['id']]['value'], + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + } + } +} + +// If we show project dropdown, add controls for project and client. +$project_list = $client_list = array(); +if ($showProject) { // Dropdown for projects assigned to user. - $project_list = $user->getAssignedProjects(); + $options['include_templates'] = $user->isPluginEnabled('tp') && $config->getDefinedValue('bind_templates_with_projects'); + $project_list = $user->getAssignedProjects($options); $form->addInput(array('type'=>'combobox', - 'onchange'=>'fillTaskDropdown(this.value);', + 'onchange'=>'fillTaskDropdown(this.value);fillTemplateDropdown(this.value);prepopulateNote();', 'name'=>'project', - 'style'=>'width: 250px;', 'value'=>$cl_project, 'data'=>$project_list, 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); - // Dropdown for clients if the clients plugin is enabled. - if ($user->isPluginEnabled('cl')) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); + // Client dropdown. + if ($showClient) { + $active_clients = ttGroupHelper::getActiveClients(true); // We need an array of assigned project ids to do some trimming. foreach($project_list as $project) $projects_assigned_to_user[] = $project['id']; @@ -152,109 +198,142 @@ $form->addInput(array('type'=>'combobox', 'onchange'=>'fillProjectDropdown(this.value);', 'name'=>'client', - 'style'=>'width: 250px;', 'value'=>$cl_client, 'data'=>$client_list, 'datakeys'=>array('id', 'name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); } } -if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - $task_list = ttTeamHelper::getActiveTasks($user->team_id); +// Task dropdown. +$task_list = array(); +if ($showTask) { + $task_list = ttGroupHelper::getActiveTasks(); $form->addInput(array('type'=>'combobox', 'name'=>'task', - 'style'=>'width: 250px;', 'value'=>$cl_task, 'data'=>$task_list, 'datakeys'=>array('id','name'), - 'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); } -// Add other controls. -if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) { +// Start and finish controls. +if ($showStart) { $form->addInput(array('type'=>'text','name'=>'start','value'=>$cl_start,'onchange'=>"formDisable('start');")); $form->addInput(array('type'=>'text','name'=>'finish','value'=>$cl_finish,'onchange'=>"formDisable('finish');")); + if ($user->punch_mode && !$user->canOverridePunchMode()) { + // Make the start and finish fields read-only. + $form->getElement('start')->setEnabled(false); + $form->getElement('finish')->setEnabled(false); + } } -if (!$user->canManageTeam() && defined('READONLY_START_FINISH') && isTrue(READONLY_START_FINISH)) { - // Make the start and finish fields read-only. - $form->getElement('start')->setEnable(false); - $form->getElement('finish')->setEnable(false); + +// Duration control. +if ($showDuration) { + $placeholder = $user->getDecimalMark() == ',' ? str_replace('.', ',', $i18n->get('form.time.duration_placeholder')) : $i18n->get('form.time.duration_placeholder'); + $form->addInput(array('type'=>'text','name'=>'duration','placeholder'=>$placeholder,'value'=>$cl_duration,'onchange'=>"formDisable('duration');")); } -if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type)) - $form->addInput(array('type'=>'text','name'=>'duration','value'=>$cl_duration,'onchange'=>"formDisable('duration');")); + +// Date field. $form->addInput(array('type'=>'datefield','name'=>'date','maxlength'=>'20','value'=>$cl_date)); -$form->addInput(array('type'=>'textarea','name'=>'note','style'=>'width: 250px; height: 200px;','value'=>$cl_note)); -// If we have custom fields - add controls for them. -if ($custom_fields && $custom_fields->fields[0]) { - // Only one custom field is supported at this time. - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) { - $form->addInput(array('type'=>'text','name'=>'cf_1','value'=>$cl_cf_1)); - } elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) { + +// If we have templates, add a dropdown to select one. +if ($user->isPluginEnabled('tp')){ + $template_list = ttGroupHelper::getActiveTemplates(); + if (count($template_list) >= 1) { $form->addInput(array('type'=>'combobox', - 'name'=>'cf_1', - 'style'=>'width: 250px;', - 'value'=>$cl_cf_1, - 'data'=>$custom_fields->options, - 'empty' => array('' => $i18n->getKey('dropdown.select')))); + 'onchange'=>'fillNote(this.value);', + 'name'=>'template', + 'data'=>$template_list, + 'datakeys'=>array('id','name'), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $smarty->assign('template_dropdown', 1); + $smarty->assign('bind_templates_with_projects', $config->getDefinedValue('bind_templates_with_projects')); + $smarty->assign('prepopulate_note', $config->getDefinedValue('prepopulate_note')); + $smarty->assign('template_list', $template_list); } } + +// Note control. +$form->addInput(array('type'=>'textarea','name'=>'note','value'=>$cl_note)); + // Hidden control for record id. $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); -if ($user->isPluginEnabled('iv')) - $form->addInput(array('type'=>'checkbox','name'=>'billable','data'=>1,'value'=>$cl_billable)); + +// A hidden control for today's date from user's browser. $form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_save or btn_copy click. -$form->addInput(array('type'=>'submit','name'=>'btn_save','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.save'))); -$form->addInput(array('type'=>'submit','name'=>'btn_copy','onclick'=>'browser_today.value=get_date()','value'=>$i18n->getKey('button.copy'))); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); + +// Copy button. +$on_click_action = 'browser_today.value=get_date();'; +$form->addInput(array('type'=>'submit','name'=>'btn_copy','onclick'=>$on_click_action,'value'=>$i18n->get('button.copy'))); + +// Save button. +if ($confirm_save) $on_click_action .= 'return(confirmSave());'; +$form->addInput(array('type'=>'submit','name'=>'btn_save','onclick'=>$on_click_action,'value'=>$i18n->get('button.save'))); + +// Delete button. +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); if ($request->isPost()) { // Validate user input. - if ($user->isPluginEnabled('cl') && $user->isPluginEnabled('cm') && !$cl_client) - $err->add($i18n->getKey('error.client')); - if ($custom_fields) { - if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $err->add($i18n->getKey('error.field'), $custom_fields->fields[0]['label']); + if ($showClient && $user->isOptionEnabled('client_required') && !$cl_client) + $err->add($i18n->get('error.client')); + // Validate input in time custom fields. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($timeCustomFields as $timeField) { + // Validation is the same for text and dropdown fields. + if (!ttValidString($timeField['value'], !$timeField['required'])) $err->add($i18n->get('error.field'), htmlspecialchars($timeField['label'])); + } + } + if ($showProject) { + if (!$cl_project) $err->add($i18n->get('error.project')); } - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $err->add($i18n->getKey('error.project')); + if ($showTask && $taskRequired) { + if (!$cl_task) $err->add($i18n->get('error.task')); } - if (!$cl_duration) { - if ('0' == $cl_duration) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); - elseif ($cl_start || $cl_finish) { + if ($cl_duration == null) { + if ($cl_start || $cl_finish) { if (!ttTimeHelper::isValidTime($cl_start)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.start')); + $err->add($i18n->get('error.field'), $i18n->get('label.start')); if ($cl_finish) { if (!ttTimeHelper::isValidTime($cl_finish)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.finish')); + $err->add($i18n->get('error.field'), $i18n->get('label.finish')); if (!ttTimeHelper::isValidInterval($cl_start, $cl_finish)) - $err->add($i18n->getKey('error.interval'), $i18n->getKey('label.finish'), $i18n->getKey('label.start')); + $err->add($i18n->get('error.interval'), $i18n->get('label.finish'), $i18n->get('label.start')); } } else { - if ((TYPE_START_FINISH == $user->record_type) || (TYPE_ALL == $user->record_type)) { - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.start')); - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish')); + if ($showStart) { + $err->add($i18n->get('error.empty'), $i18n->get('label.start')); + $err->add($i18n->get('error.empty'), $i18n->get('label.finish')); } - if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type)) - $err->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration')); + if ($showDuration) + $err->add($i18n->get('error.empty'), $i18n->get('label.duration')); } } else { - if (!ttTimeHelper::isValidDuration($cl_duration)) - $err->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); + if (false === ttTimeHelper::postedDurationToMinutes($cl_duration)) + $err->add($i18n->get('error.field'), $i18n->get('label.duration')); } - if (!ttValidDate($cl_date)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.date')); - if (!ttValidString($cl_note, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.note')); + if (!ttValidDate($cl_date)) $err->add($i18n->get('error.field'), $i18n->get('label.date')); + if (!ttValidString($cl_note, true)) $err->add($i18n->get('error.field'), $i18n->get('label.note')); + if ($user->isPluginEnabled('tp') && !ttValidTemplateText($cl_note)) { + $err->add($i18n->get('error.field'), $i18n->get('label.note')); + } + if (!ttTimeHelper::canAdd()) $err->add($i18n->get('error.expired')); // Finished validating user input. // This is a new date for the time record. - $new_date = new DateAndTime($user->date_format, $cl_date); + $new_date = null; + if ($err->no()) + $new_date = new ttDate($cl_date, $user->getDateFormat()); // Prohibit creating entries in future. - if (defined('FUTURE_ENTRIES') && !isTrue(FUTURE_ENTRIES)) { - $browser_today = new DateAndTime(DB_DATEFORMAT, $request->getParameter('browser_today', null)); - if ($new_date->after($browser_today)) - $err->add($i18n->getKey('error.future_date')); + if ($err->no() && !$user->isOptionEnabled('future_entries')) { + $browser_today = new ttDate($request->getParameter('browser_today', null)); + $server_tomorrow = new ttDate(); + $server_tomorrow->incrementDay(); + if ($new_date->after($browser_today) || $new_date->after($server_tomorrow)) + $err->add($i18n->get('error.future_date')); } // Save record. @@ -268,35 +347,34 @@ if ($err->no()) { // 1) Prohibit saving locked entries in any form. if ($user->isDateLocked($item_date)) - $err->add($i18n->getKey('error.range_locked')); + $err->add($i18n->get('error.range_locked')); // 2) Prohibit saving completed unlocked entries into locked range. if ($err->no() && $user->isDateLocked($new_date)) - $err->add($i18n->getKey('error.range_locked')); + $err->add($i18n->get('error.range_locked')); // 3) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists. - $uncompleted = ($cl_finish == '' && $cl_duration == ''); - if ($uncompleted) { - $not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser()); + $uncompleted = ($cl_finish == null && $cl_duration == null); + if ($uncompleted && $oneUncompleted) { + $not_completed_rec = ttTimeHelper::getUncompleted($user_id); if ($not_completed_rec && ($time_rec['id'] <> $not_completed_rec['id'])) { // We have another not completed record. - $err->add($i18n->getKey('error.uncompleted_exists')." ".$i18n->getKey('error.goto_uncompleted').""); + $err->add($i18n->get('error.uncompleted_exists')." ".$i18n->get('error.goto_uncompleted').""); } } } // Prohibit creating an overlapping record. if ($err->no()) { - if (ttTimeHelper::overlaps($user->getActiveUser(), $new_date->toString(DB_DATEFORMAT), $cl_start, $cl_finish, $cl_id)) - $err->add($i18n->getKey('error.overlap')); + if (ttTimeHelper::overlaps($user_id, $new_date->toString(), $cl_start, $cl_finish, $cl_id)) + $err->add($i18n->get('error.overlap')); } // Now, an update. if ($err->no()) { $res = ttTimeHelper::update(array( 'id'=>$cl_id, - 'date'=>$new_date->toString(DB_DATEFORMAT), - 'user_id'=>$user->getActiveUser(), + 'date'=>$new_date->toString(), 'client'=>$cl_client, 'project'=>$cl_project, 'task'=>$cl_task, @@ -304,24 +382,23 @@ 'finish'=>$cl_finish, 'duration'=>$cl_duration, 'note'=>$cl_note, - 'billable'=>$cl_billable)); - - // If we have custom fields - update values. - if ($res && $custom_fields) { - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) - $res = $custom_fields->update($cl_id, $custom_fields->fields[0]['id'], null, $cl_cf_1); - elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) - $res = $custom_fields->update($cl_id, $custom_fields->fields[0]['id'], $cl_cf_1, null); + 'billable'=>$cl_billable, + 'paid'=>$cl_paid)); + + // Update time custom fields if we have them. + if ($res && isset($custom_fields) && $custom_fields->timeFields) { + $res = $custom_fields->updateTimeFields($cl_id, $timeCustomFields); } if ($res) { - header('Location: time.php?date='.$new_date->toString(DB_DATEFORMAT)); + header('Location: time.php?date='.$new_date->toString()); exit(); } + $err->add($i18n->get('error.db')); } } - // Save as new record. + // Copy record. if ($request->getParameter('btn_copy')) { // We need to: // 1) Prohibit saving into locked range. @@ -331,31 +408,30 @@ if ($err->no()) { // 1) Prohibit saving into locked range. if ($user->isDateLocked($new_date)) - $err->add($i18n->getKey('error.range_locked')); + $err->add($i18n->get('error.range_locked')); // 2) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists. $uncompleted = ($cl_finish == '' && $cl_duration == ''); - if ($uncompleted) { - $not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser()); + if ($uncompleted && $oneUncompleted) { + $not_completed_rec = ttTimeHelper::getUncompleted($user_id); if ($not_completed_rec) { // We have another not completed record. - $err->add($i18n->getKey('error.uncompleted_exists')." ".$i18n->getKey('error.goto_uncompleted').""); + $err->add($i18n->get('error.uncompleted_exists')." ".$i18n->get('error.goto_uncompleted').""); } } } // Prohibit creating an overlapping record. if ($err->no()) { - if (ttTimeHelper::overlaps($user->getActiveUser(), $new_date->toString(DB_DATEFORMAT), $cl_start, $cl_finish)) - $err->add($i18n->getKey('error.overlap')); + if (ttTimeHelper::overlaps($user_id, $new_date->toString(), $cl_start, $cl_finish)) + $err->add($i18n->get('error.overlap')); } // Now, a new insert. if ($err->no()) { $id = ttTimeHelper::insert(array( - 'date'=>$new_date->toString(DB_DATEFORMAT), - 'user_id'=>$user->getActiveUser(), + 'date'=>$new_date->toString(), 'client'=>$cl_client, 'project'=>$cl_project, 'task'=>$cl_task, @@ -363,21 +439,19 @@ 'finish'=>$cl_finish, 'duration'=>$cl_duration, 'note'=>$cl_note, - 'billable'=>$cl_billable)); + 'billable'=>$cl_billable, + 'paid'=>$cl_paid)); - // Insert a custom field if we have it. + // Insert time custom fields if we have them. $res = true; - if ($id && $custom_fields && $cl_cf_1) { - if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_TEXT) - $res = $custom_fields->insert($id, $custom_fields->fields[0]['id'], null, $cl_cf_1); - elseif ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) - $res = $custom_fields->insert($id, $custom_fields->fields[0]['id'], $cl_cf_1, null); + if ($id && isset($custom_fields) && $custom_fields->timeFields) { + $res = $custom_fields->insertTimeFields($id, $timeCustomFields); } if ($id && $res) { - header('Location: time.php?date='.$new_date->toString(DB_DATEFORMAT)); + header('Location: time.php?date='.$new_date->toString()); exit(); } - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } @@ -387,11 +461,23 @@ } } // isPost +if ($confirm_save) { + $smarty->assign('confirm_save', true); + $smarty->assign('entry_date', $cl_date); +} +$smarty->assign('show_client', $showClient); +$smarty->assign('show_billable', $showBillable); +$smarty->assign('show_paid_status', $showPaidStatus); +$smarty->assign('show_project', $showProject); +$smarty->assign('show_task', $showTask); +$smarty->assign('task_required', $taskRequired); +$smarty->assign('show_start', $showStart); +$smarty->assign('show_duration', $showDuration); $smarty->assign('client_list', $client_list); $smarty->assign('project_list', $project_list); $smarty->assign('task_list', $task_list); $smarty->assign('forms', array($form->getName()=>$form->toArray())); $smarty->assign('onload', 'onLoad="fillDropdowns()"'); -$smarty->assign('title', $i18n->getKey('title.edit_time_record')); +$smarty->assign('title', $i18n->get('title.edit_time_record')); $smarty->assign('content_page_name', 'time_edit.tpl'); $smarty->display('index.tpl'); diff --git a/time_files.php b/time_files.php new file mode 100644 index 000000000..c5dce5baa --- /dev/null +++ b/time_files.php @@ -0,0 +1,66 @@ +isPluginEnabled('at')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_id = (int)$request->getParameter('id'); +$time_rec = ttTimeHelper::getRecordForFileView($cl_id); +if (!$time_rec) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$cl_description = ''; +if ($request->isPost()) { + $cl_description = trim($request->getParameter('description')); +} + +$fileHelper = new ttFileHelper($err); +$files = $fileHelper::getEntityFiles($cl_id, 'time'); + +$form = new Form('fileUploadForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_id)); +$form->addInput(array('type'=>'upload','name'=>'newfile','value'=>$i18n->get('button.submit'))); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.add'))); + +if ($request->isPost()) { + // We are adding a new file. + + // Validate user input. + if (!$_FILES['newfile']['name']) $err->add($i18n->get('error.upload')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + // Finished validating user input. + + if ($err->no()) { + $fields = array('entity_type'=>'time', + 'entity_id' => $cl_id, + 'file_name' => $_FILES['newfile']['name'], + 'description'=>$cl_description); + if ($fileHelper->putFile($fields)) { + header('Location: time_files.php?id='.$cl_id); + exit(); + } + } +} // isPost + +$smarty->assign('can_edit', $time_rec['can_edit']); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('files', $files); +$smarty->assign('title', $i18n->get('title.time_files')); +$smarty->assign('content_page_name', 'entity_files.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheet_add.php b/timesheet_add.php new file mode 100644 index 000000000..b52775f2f --- /dev/null +++ b/timesheet_add.php @@ -0,0 +1,85 @@ +isPluginEnabled('ts')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. + +$cl_name = $cl_client = $cl_project = $cl_start = $cl_finish = $cl_comment = null; +if ($request->isPost()) { + $cl_name = trim($request->getParameter('timesheet_name')); + $cl_client = $request->getParameter('client'); + $cl_project = $request->getParameter('project'); + $cl_start = $request->getParameter('start'); + $cl_finish = $request->getParameter('finish'); + $cl_comment = trim($request->getParameter('comment')); +} + +$user_id = $user->getUser(); + +$form = new Form('timesheetForm'); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'timesheet_name','value'=>$cl_name)); + +// Dropdown for clients if the clients plugin is enabled. +$showClient = $user->isPluginEnabled('cl'); +if ($showClient) { + $clients = ttGroupHelper::getActiveClients(); + $form->addInput(array('type'=>'combobox','name'=>'client','data'=>$clients,'datakeys'=>array('id','name'),'value'=>$cl_client,'empty'=>array(''=>$i18n->get('dropdown.select')))); +} +// Dropdown for projects. +$showProject = MODE_PROJECTS == $user->getTrackingMode() || MODE_PROJECTS_AND_TASKS == $user->getTrackingMode(); +if ($showProject) { + $projects = $user->getAssignedProjects(); + $form->addInput(array('type'=>'combobox','name'=>'project','data'=>$projects,'datakeys'=>array('id','name'),'value'=>$cl_project,'empty'=>array(''=>$i18n->get('dropdown.all')))); +} +$form->addInput(array('type'=>'datefield','maxlength'=>'20','name'=>'start','value'=>$cl_start)); +$form->addInput(array('type'=>'datefield','maxlength'=>'20','name'=>'finish','value'=>$cl_finish)); +$form->addInput(array('type'=>'textarea','name'=>'comment','value'=>$cl_comment)); +$form->addInput(array('type'=>'submit','name'=>'btn_add','value'=>$i18n->get('button.add'))); + +if ($request->isPost()) { + // Validate user input. + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidDate($cl_start)) $err->add($i18n->get('error.field'), $i18n->get('label.start_date')); + if (!ttValidDate($cl_finish)) $err->add($i18n->get('error.field'), $i18n->get('label.end_date')); + if (!ttValidString($cl_comment, true)) $err->add($i18n->get('error.field'), $i18n->get('label.comment')); + if ($err->no() && ttTimesheetHelper::getTimesheetByName($cl_name)) $err->add($i18n->get('error.object_exists')); + $fields = array('user_id' => $user_id, + 'name' => $cl_name, + 'client_id' => $cl_client, + 'project_id' => $cl_project, + 'start_date' => $cl_start, + 'end_date' => $cl_finish, + 'comment' => $cl_comment); + if ($err->no() && !ttTimesheetHelper::timesheetItemsExist($fields)) $err->add($i18n->get('error.no_records')); + if ($err->no() && ttTimesheetHelper::overlaps($fields)) $err->add($i18n->get('error.overlap')); + // Finished validating user input. + + if ($err->no()) { + if (ttTimesheetHelper::createTimesheet($fields)) { + header('Location: timesheets.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.timesheetForm.timesheet_name.focus()"'); +$smarty->assign('show_client', $showClient); +$smarty->assign('show_project', $showProject); +$smarty->assign('title', $i18n->get('title.add_timesheet')); +$smarty->assign('content_page_name', 'timesheet_add.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheet_delete.php b/timesheet_delete.php new file mode 100644 index 000000000..ff1e4e180 --- /dev/null +++ b/timesheet_delete.php @@ -0,0 +1,51 @@ +isPluginEnabled('ts')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_timesheet_id = (int)$request->getParameter('id'); +$timesheet = ttTimesheetHelper::getTimesheet($cl_timesheet_id); +if (!$timesheet) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$timesheet_to_delete = $timesheet['name']; + +$form = new Form('timesheetDeleteForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_timesheet_id)); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); + +if ($request->isPost()) { + if ($request->getParameter('btn_delete')) { + if (ttTimesheetHelper::delete($cl_timesheet_id)) { + header('Location: timesheets.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } elseif ($request->getParameter('btn_cancel')) { + header('Location: timesheets.php'); + exit(); + } +} // isPost + +$smarty->assign('timesheet_to_delete', $timesheet_to_delete); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.invoiceDeleteForm.btn_cancel.focus()"'); +$smarty->assign('title', $i18n->get('title.delete_timesheet')); +$smarty->assign('content_page_name', 'timesheet_delete.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheet_edit.php b/timesheet_edit.php new file mode 100644 index 000000000..3e46733ae --- /dev/null +++ b/timesheet_edit.php @@ -0,0 +1,86 @@ +isPluginEnabled('ts')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_timesheet_id = (int)$request->getParameter('id'); +$timesheet = ttTimesheetHelper::getTimesheet($cl_timesheet_id); +if (!$timesheet) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +if ($request->isPost()) { + $cl_name = trim($request->getParameter('timesheet_name')); + $cl_comment = trim($request->getParameter('comment')); + $cl_status = $request->getParameter('status'); +} else { + $cl_name = $timesheet['name']; + $cl_comment = $timesheet['comment']; + $cl_status = $timesheet['status']; +} + +// Can we delete this timesheet? +$canDelete = $timesheet['approve_status'] != 1 + || (($user->id == $timesheet['user_id'] && $user->can('approve_own_timesheets')) + || ($user->id != $timesheet['user_id'] && $user->can('approve_timesheets'))); + +$form = new Form('timesheetForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_timesheet_id)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'timesheet_name','value'=>$cl_name)); +$form->addInput(array('type'=>'textarea','name'=>'comment','value'=>$cl_comment)); +$form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status, + 'data'=>array(ACTIVE=>$i18n->get('dropdown.status_active'),INACTIVE=>$i18n->get('dropdown.status_inactive')))); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); +if ($canDelete) $form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); + +if ($request->isPost()) { + // Validate user input. + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.thing_name')); + if (!ttValidString($cl_comment, true)) $err->add($i18n->get('error.field'), $i18n->get('label.comment')); + if (!ttValidStatus($cl_status)) $err->add($i18n->get('error.field'), $i18n->get('label.status')); + + if ($request->getParameter('btn_save')) { + if ($err->no()) { + $existing_timesheet = ttTimesheetHelper::getTimesheetByName($cl_name); + if (!$existing_timesheet || ($cl_timesheet_id == $existing_timesheet['id'])) { + // Update timesheet information. + if (ttTimesheetHelper::update(array( + 'id' => $cl_timesheet_id, + 'name' => $cl_name, + 'comment' => $cl_comment, + 'status' => $cl_status))) { + header('Location: timesheets.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } else + $err->add($i18n->get('error.object_exists')); + } + } + + if ($request->getParameter('btn_delete') && $canDelete) { + header("Location: timesheet_delete.php?id=$cl_timesheet_id"); + exit(); + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.timesheetForm.timesheet_name.focus()"'); +$smarty->assign('can_delete', $canDelete); +$smarty->assign('title', $i18n->get('title.edit_timesheet')); +$smarty->assign('content_page_name', 'timesheet_edit.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheet_files.php b/timesheet_files.php new file mode 100644 index 000000000..bec4e29d8 --- /dev/null +++ b/timesheet_files.php @@ -0,0 +1,66 @@ +isPluginEnabled('ts')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_timesheet_id = (int)$request->getParameter('id'); +$timesheet = ttTimesheetHelper::getTimesheet($cl_timesheet_id); +if (!$timesheet) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + +$cl_description = null; +if ($request->isPost()) { + $cl_description = trim($request->getParameter('description')); +} + +$fileHelper = new ttFileHelper($err); +$files = $fileHelper::getEntityFiles($cl_timesheet_id, 'timesheet'); + +$form = new Form('fileUploadForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$cl_timesheet_id)); +$form->addInput(array('type'=>'upload','name'=>'newfile','value'=>$i18n->get('button.submit'))); +$form->addInput(array('type'=>'textarea','name'=>'description','value'=>$cl_description)); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.add'))); + +if ($request->isPost()) { + // We are adding a new file. + + // Validate user input. + if (!$_FILES['newfile']['name']) $err->add($i18n->get('error.upload')); + if (!ttValidString($cl_description, true)) $err->add($i18n->get('error.field'), $i18n->get('label.description')); + // Finished validating user input. + + if ($err->no()) { + $fields = array('entity_type'=>'timesheet', + 'entity_id' => $cl_timesheet_id, + 'file_name' => $_FILES['newfile']['name'], + 'description'=>$cl_description); + if ($fileHelper->putFile($fields)) { + header('Location: timesheet_files.php?id='.$cl_timesheet_id); + exit(); + } + } +} // isPost + +$smarty->assign('can_edit', true); // Relying on access checks above. +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('files', $files); +$smarty->assign('title', $i18n->get('title.timesheet_files').': '.$timesheet['name']); +$smarty->assign('content_page_name', 'entity_files.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheet_view.php b/timesheet_view.php new file mode 100644 index 000000000..b4eda31f7 --- /dev/null +++ b/timesheet_view.php @@ -0,0 +1,130 @@ +isPluginEnabled('ts')) { + header('Location: feature_disabled.php'); + exit(); +} +$cl_timesheet_id = (int)$request->getParameter('id'); +$timesheet = ttTimesheetHelper::getTimesheet($cl_timesheet_id); +if (!$timesheet) { + header('Location: access_denied.php'); + exit(); +} +// TODO: add other checks here for timesheet being appropriate for user role. +// TODO: if this is a timesheet submit, validate approver id, too. +// End of access checks. + +if ($request->isPost()) { + $cl_comment = trim($request->getParameter('comment')); + $approver_id = $request->getParameter('approver'); +} + +$options = ttTimesheetHelper::getReportOptions($timesheet); +$subtotals = ttReportHelper::getSubtotals($options); +$totals = ttReportHelper::getTotals($options); + +// Determine which controls to show and obtain date for them. +$showApprovers = false; +$showSubmit = !$timesheet['submit_status']; +if ($showSubmit) { + $approvers = ttTimesheetHelper::getApprovers(); + $showApprovers = count($approvers) >= 1; +} +$canApprove = $user->can('approve_timesheets') || $user->can('approve_own_timesheets'); +$showApprove = $timesheet['submit_status'] && $timesheet['approve_status'] == null && $canApprove; + +// Add a form with controls. +$form = new Form('timesheetForm'); +$form->addInput(array('type'=>'hidden','name'=>'id','value'=>$timesheet['id'])); + +if ($showSubmit) { + if ($showApprovers) { + $form->addInput(array('type'=>'combobox', + 'name'=>'approver', + 'style'=>'width: 200px;', + 'data'=>$approvers, + 'datakeys'=>array('id','name','email'))); + } + $form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.submit'))); +} + +if ($showApprove) { + $form->addInput(array('type'=>'textarea','name'=>'comment','maxlength'=>'250')); + $form->addInput(array('type'=>'submit','name'=>'btn_approve','value'=>$i18n->get('button.approve'))); + $form->addInput(array('type'=>'submit','name'=>'btn_disapprove','value'=>$i18n->get('button.disapprove'))); +} + +// Submit. +if ($request->isPost()) { + if ($request->getParameter('btn_submit')) { + $fields = array('timesheet_id' => $timesheet['id'], + 'approver_id' => $approver_id); + if (!ttTimesheetHelper::markSubmitted($fields)) + $err->add($i18n->get('error.db')); + if ($err->no() && !ttTimesheetHelper::sendSubmitEmail($fields)) { + $err->add($i18n->get('error.mail_send')); + } + if ($err->no()) { + // Redirect to self. + header('Location: timesheet_view.php?id='.$timesheet['id']); + exit(); + } + } + + if ($request->getParameter('btn_approve')) { + $fields = array('timesheet_id' => $timesheet['id'], + 'name' => $timesheet['name'], + 'user_id' => $timesheet['user_id'], + 'comment' => $cl_comment); + if (!ttTimesheetHelper::markApproved($fields)) + $err->add($i18n->get('error.db')); + if ($err->no() && !ttTimesheetHelper::sendApprovedEmail($fields)) { + $err->add($i18n->get('error.mail_send')); + } + if ($err->no()) { + // Redirect to self. + header('Location: timesheet_view.php?id='.$timesheet['id']); + exit(); + } + } + + if ($request->getParameter('btn_disapprove')) { + $fields = array('timesheet_id' => $timesheet['id'], + 'name' => $timesheet['name'], + 'user_id' => $timesheet['user_id'], + 'comment' => $cl_comment); + if (!ttTimesheetHelper::markDisapproved($fields)) + $err->add($i18n->get('error.db')); + if ($err->no() && !ttTimesheetHelper::sendDisapprovedEmail($fields)) { + $err->add($i18n->get('error.mail_send')); + } + if ($err->no()) { + // Redirect to self. + header('Location: timesheet_view.php?id='.$timesheet['id']); + exit(); + } + } +} + +$smarty->assign('group_by_header', ttReportHelper::makeGroupByHeader($options)); +$smarty->assign('timesheet', $timesheet); +$smarty->assign('subtotals', $subtotals); +$smarty->assign('totals', $totals); +$smarty->assign('show_approvers', $showApprovers); +$smarty->assign('show_submit', $showSubmit); +$smarty->assign('show_approve', $showApprove); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.timesheet').": ".$timesheet['start_date']." - ".$timesheet['end_date']); +$smarty->assign('content_page_name', 'timesheet_view.tpl'); +$smarty->display('index.tpl'); diff --git a/timesheets.php b/timesheets.php new file mode 100644 index 000000000..7dbbb78df --- /dev/null +++ b/timesheets.php @@ -0,0 +1,82 @@ +behalf_id && (!$user->can('track_time') || !$user->checkBehalfId())) { + header('Location: access_denied.php'); // Trying on behalf, but no right or wrong user. + exit(); +} +if (!$user->behalf_id && !$user->can('track_own_time') && !$user->adjustBehalfId()) { + header('Location: access_denied.php'); // Trying as self, but no right for self, and noone to work on behalf. + exit(); +} +if (!$user->isPluginEnabled('ts')) { + header('Location: feature_disabled.php'); + exit(); +} +if ($request->isPost()) { + $userChanged = (bool)$request->getParameter('user_changed'); // Reused in multiple places below. + if ($userChanged && !($user->can('track_time') && $user->isUserValid((int)$request->getParameter('user')))) { + header('Location: access_denied.php'); // Group changed, but no rght or wrong user id. + exit(); + } +} +// End of access checks. + +// Determine user for whom we display this page. +if ($request->isPost() && $userChanged) { + $user_id = (int)$request->getParameter('user'); + $user->setOnBehalfUser($user_id); +} else { + $user_id = $user->getUser(); +} + +$group_id = $user->getGroup(); + +$showFiles = $user->isPluginEnabled('at'); + +// Elements of timesheetsForm. +$form = new Form('timesheetsForm'); + +if ($user->can('track_time')) { + $rank = $user->getMaxRankForGroup($group_id); + if ($user->can('track_own_time')) + $options = array('status'=>ACTIVE,'max_rank'=>$rank,'include_self'=>true,'self_first'=>true); + else + $options = array('status'=>ACTIVE,'max_rank'=>$rank); + $user_list = $user->getUsers($options); + if (count($user_list) >= 1) { + $form->addInput(array('type'=>'combobox', + 'onchange'=>'document.timesheetsForm.user_changed.value=1;document.timesheetsForm.submit();', + 'name'=>'user', + 'value'=>$user_id, + 'data'=>$user_list, + 'datakeys'=>array('id','name'))); + $form->addInput(array('type'=>'hidden','name'=>'user_changed')); + $smarty->assign('user_dropdown', 1); + } +} + +$active_timesheets = ttTimesheetHelper::getActiveTimesheets(); +$inactive_timesheets = ttTimesheetHelper::getInactiveTimesheets(); + +$showClient = $user->isPluginEnabled('cl'); + +$smarty->assign('active_timesheets', $active_timesheets); +$smarty->assign('inactive_timesheets', $inactive_timesheets); +$smarty->assign('show_client', $showClient); +$smarty->assign('show_files', $showFiles); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.timesheets')); +$smarty->assign('content_page_name', 'timesheets.tpl'); +$smarty->display('index.tpl'); diff --git a/tofile.php b/tofile.php index c7f81e502..0134885f8 100644 --- a/tofile.php +++ b/tofile.php @@ -1,48 +1,27 @@ isPluginEnabled('cf')) { require_once('plugins/CustomFields.class.php'); - $custom_fields = new CustomFields($user->team_id); + $custom_fields = new CustomFields(); } +$show_cost_per_hour = $user->getConfigOption('report_cost_per_hour') && ($user->can('manage_invoices') || $user->isClient()); + // Report settings are stored in session bean before we get here. $bean = new ActionForm('reportBean', new Form('reportForm'), $request); @@ -53,17 +32,18 @@ $type = $request->getParameter('type'); // Also, there are 2 variations of report: totals only, or normal. Totals only means that the report -// is grouped by (either date, user, client, project, task or cf_1) and user only needs to see subtotals by group. +// is grouped by (either date, user, client, project, or task) and user only needs to see subtotals by group. $totals_only = $bean->getAttribute('chtotalsonly'); // Obtain items. +$options = ttReportHelper::getReportOptions($bean); if ($totals_only) - $subtotals = ttReportHelper::getSubtotals($bean); + $subtotals = ttReportHelper::getSubtotals($options); else - $items = ttReportHelper::getItems($bean); + $items = ttReportHelper::getItems($options); // Build a string to use as filename for the files being downloaded. -$filename = strtolower($i18n->getKey('title.report')).'_'.$bean->mValues['start_date'].'_'.$bean->mValues['end_date']; +$filename = strtolower($i18n->get('title.report')).'_'.$bean->mValues['start_date'].'_'.$bean->mValues['end_date']; header('Pragma: public'); // This is needed for IE8 to download files over https. header('Content-Type: text/html; charset=utf-8'); @@ -82,21 +62,26 @@ print "\n"; print "\n"; - $group_by = $bean->getAttribute('group_by'); if ($totals_only) { - // Totals only report. Print subtotals. + // Totals only report. + $group_by_tag = ttReportHelper::makeGroupByXmlTag($options); + + // Print subtotals. foreach ($subtotals as $subtotal) { print "\n"; - print "\t<".$group_by.">\n"; + print "\t<".$group_by_tag.">\n"; if ($bean->getAttribute('chduration')) { $val = $subtotal['time']; - if($val && defined('EXPORT_DECIMAL_DURATION') && isTrue(EXPORT_DECIMAL_DURATION)) + if($val && isTrue('EXPORT_DECIMAL_DURATION')) $val = time_to_decimal($val); print "\t\n"; } + if ($bean->getAttribute('chunits')) { + print "\t\n"; + } if ($bean->getAttribute('chcost')) { print "\tcanManageTeam() || $user->isClient()) + if ($user->can('manage_invoices') || $user->isClient()) print $subtotal['cost']; else print $subtotal['expenses']; @@ -110,29 +95,65 @@ print "\n"; print "\t\n"; - if ($user->canManageTeam() || $user->isClient()) print "\t\n"; + if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) print "\t\n"; + // User custom fields. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) print "\t<$field_name>\n"; + } + } if ($bean->getAttribute('chclient')) print "\t\n"; if ($bean->getAttribute('chproject')) print "\t\n"; + // Project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) print "\t<$field_name>\n"; + } + } if ($bean->getAttribute('chtask')) print "\t\n"; - if ($bean->getAttribute('chcf_1')) print "\t\n"; + // Time custom fields. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) print "\t<$field_name>\n"; + } + } if ($bean->getAttribute('chstart')) print "\t\n"; if ($bean->getAttribute('chfinish')) print "\t\n"; if ($bean->getAttribute('chduration')) { $duration = $item['duration']; - if($duration && defined('EXPORT_DECIMAL_DURATION') && isTrue(EXPORT_DECIMAL_DURATION)) + if($duration && isTrue('EXPORT_DECIMAL_DURATION')) $duration = time_to_decimal($duration); print "\t\n"; } + if ($bean->getAttribute('chunits')) print "\t\n"; if ($bean->getAttribute('chnote')) print "\t\n"; + if ($bean->getAttribute('chcost') && $show_cost_per_hour) { + print "\t\n"; + } if ($bean->getAttribute('chcost')) { print "\tcanManageTeam() || $user->isClient()) + if ($user->can('manage_invoices') || $user->isClient()) print $item['cost']; else print $item['expense']; print "]]>\n"; } + if ($bean->getAttribute('chapproved')) print "\t\n"; + if ($bean->getAttribute('chpaid')) print "\t\n"; + if ($bean->getAttribute('chip')) { + $ip = $item['modified'] ? $item['modified_ip'].' '.$item['modified'] : $item['created_ip'].' '.$item['created']; + print "\t\n"; + } if ($bean->getAttribute('chinvoice')) print "\t\n"; + if ($bean->getAttribute('chtimesheet')) print "\t\n"; print "\n"; } @@ -150,22 +171,15 @@ $bom = chr(239).chr(187).chr(191); // 0xEF 0xBB 0xBF in the beginning of the file is UTF8 BOM. print $bom; // Without this Excel does not display UTF8 characters properly. - $group_by = $bean->getAttribute('group_by'); if ($totals_only) { // Totals only report. - - // Determine group_by header. - if ('cf_1' == $group_by) - $group_by_header = $custom_fields->fields[0]['label']; - else { - $key = 'label.'.$group_by; - $group_by_header = $i18n->getKey($key); - } + $group_by_header = ttReportHelper::makeGroupByHeader($options); // Print headers. print '"'.$group_by_header.'"'; - if ($bean->getAttribute('chduration')) print ',"'.$i18n->getKey('label.duration').'"'; - if ($bean->getAttribute('chcost')) print ',"'.$i18n->getKey('label.cost').'"'; + if ($bean->getAttribute('chduration')) print ',"'.$i18n->get('label.duration').'"'; + if ($bean->getAttribute('chunits')) print ',"'.$i18n->get('label.work_units_short').'"'; + if ($bean->getAttribute('chcost')) print ',"'.$i18n->get('label.cost').'"'; print "\n"; // Print subtotals. @@ -173,12 +187,13 @@ print '"'.$subtotal['name'].'"'; if ($bean->getAttribute('chduration')) { $val = $subtotal['time']; - if($val && defined('EXPORT_DECIMAL_DURATION') && isTrue(EXPORT_DECIMAL_DURATION)) + if($val && isTrue('EXPORT_DECIMAL_DURATION')) $val = time_to_decimal($val); print ',"'.$val.'"'; } + if ($bean->getAttribute('chunits')) print ',"'.$subtotal['units'].'"'; if ($bean->getAttribute('chcost')) { - if ($user->canManageTeam() || $user->isClient()) + if ($user->can('manage_invoices') || $user->isClient()) print ',"'.$subtotal['cost'].'"'; else print ',"'.$subtotal['expenses'].'"'; @@ -187,44 +202,110 @@ } } else { // Normal report. Print headers. - print '"'.$i18n->getKey('label.date').'"'; - if ($user->canManageTeam() || $user->isClient()) print ',"'.$i18n->getKey('label.user').'"'; - if ($bean->getAttribute('chclient')) print ',"'.$i18n->getKey('label.client').'"'; - if ($bean->getAttribute('chproject')) print ',"'.$i18n->getKey('label.project').'"'; - if ($bean->getAttribute('chtask')) print ',"'.$i18n->getKey('label.task').'"'; - if ($bean->getAttribute('chcf_1')) print ',"'.$custom_fields->fields[0]['label'].'"'; - if ($bean->getAttribute('chstart')) print ',"'.$i18n->getKey('label.start').'"'; - if ($bean->getAttribute('chfinish')) print ',"'.$i18n->getKey('label.finish').'"'; - if ($bean->getAttribute('chduration')) print ',"'.$i18n->getKey('label.duration').'"'; - if ($bean->getAttribute('chnote')) print ',"'.$i18n->getKey('label.note').'"'; - if ($bean->getAttribute('chcost')) print ',"'.$i18n->getKey('label.cost').'"'; - if ($bean->getAttribute('chinvoice')) print ',"'.$i18n->getKey('label.invoice').'"'; + print '"'.$i18n->get('label.date').'"'; + if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) print ',"'.$i18n->get('label.user').'"'; + // User custom field labels. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) print ',"'.ttNeutralizeForCsv($userField['label']).'"'; + } + } + if ($bean->getAttribute('chclient')) print ',"'.$i18n->get('label.client').'"'; + if ($bean->getAttribute('chproject')) print ',"'.$i18n->get('label.project').'"'; + // Project custom field labels. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) print ',"'.ttNeutralizeForCsv($projectField['label']).'"'; + } + } + if ($bean->getAttribute('chtask')) print ',"'.$i18n->get('label.task').'"'; + // Time custom field labels. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) print ',"'.ttNeutralizeForCsv($timeField['label']).'"'; + } + } + if ($bean->getAttribute('chstart')) print ',"'.$i18n->get('label.start').'"'; + if ($bean->getAttribute('chfinish')) print ',"'.$i18n->get('label.finish').'"'; + if ($bean->getAttribute('chduration')) print ',"'.$i18n->get('label.duration').'"'; + if ($bean->getAttribute('chunits')) print ',"'.$i18n->get('label.work_units_short').'"'; + if ($bean->getAttribute('chnote')) print ',"'.$i18n->get('label.note').'"'; + if ($bean->getAttribute('chcost')) { + if ($show_cost_per_hour) + print ',"'.$i18n->get('form.report.per_hour').'"'; + print ',"'.$i18n->get('label.cost').'"'; + } + if ($bean->getAttribute('chapproved')) print ',"'.$i18n->get('label.approved').'"'; + if ($bean->getAttribute('chpaid')) print ',"'.$i18n->get('label.paid').'"'; + if ($bean->getAttribute('chip')) print ',"'.$i18n->get('label.ip').'"'; + if ($bean->getAttribute('chinvoice')) print ',"'.$i18n->get('label.invoice').'"'; + if ($bean->getAttribute('chtimesheet')) print ',"'.$i18n->get('label.timesheet').'"'; print "\n"; // Print items. foreach ($items as $item) { print '"'.$item['date'].'"'; - if ($user->canManageTeam() || $user->isClient()) print ',"'.str_replace('"','""',$item['user']).'"'; - if ($bean->getAttribute('chclient')) print ',"'.str_replace('"','""',$item['client']).'"'; - if ($bean->getAttribute('chproject')) print ',"'.str_replace('"','""',$item['project']).'"'; - if ($bean->getAttribute('chtask')) print ',"'.str_replace('"','""',$item['task']).'"'; - if ($bean->getAttribute('chcf_1')) print ',"'.str_replace('"','""',$item['cf_1']).'"'; + if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) print ',"'.ttNeutralizeForCsv($item['user']).'"'; + // User custom fields. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) print ',"'.ttNeutralizeForCsv($item[$field_name]).'"'; + } + } + if ($bean->getAttribute('chclient')) print ',"'.ttNeutralizeForCsv($item['client']).'"'; + if ($bean->getAttribute('chproject')) print ',"'.ttNeutralizeForCsv($item['project']).'"'; + // Project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) print ',"'.ttNeutralizeForCsv($item[$field_name]).'"'; + } + } + if ($bean->getAttribute('chtask')) print ',"'.ttNeutralizeForCsv($item['task']).'"'; + // Time custom fields. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) print ',"'.ttNeutralizeForCsv($item[$field_name]).'"'; + } + } if ($bean->getAttribute('chstart')) print ',"'.$item['start'].'"'; if ($bean->getAttribute('chfinish')) print ',"'.$item['finish'].'"'; if ($bean->getAttribute('chduration')) { $val = $item['duration']; - if($val && defined('EXPORT_DECIMAL_DURATION') && isTrue(EXPORT_DECIMAL_DURATION)) + if($val && isTrue('EXPORT_DECIMAL_DURATION')) $val = time_to_decimal($val); print ',"'.$val.'"'; } - if ($bean->getAttribute('chnote')) print ',"'.str_replace('"','""',$item['note']).'"'; + if ($bean->getAttribute('chunits')) print ',"'.$item['units'].'"'; + if ($bean->getAttribute('chnote')) print ',"'.ttNeutralizeForCsv($item['note']).'"'; if ($bean->getAttribute('chcost')) { - if ($user->canManageTeam() || $user->isClient()) + if ($user->can('manage_invoices') || $user->isClient()) { + if ($show_cost_per_hour) + print ',"'.$item['cost_per_hour'].'"'; print ',"'.$item['cost'].'"'; - else + } else { print ',"'.$item['expense'].'"'; + } + } + if ($bean->getAttribute('chapproved')) print ',"'.$item['approved'].'"'; + if ($bean->getAttribute('chpaid')) print ',"'.$item['paid'].'"'; + if ($bean->getAttribute('chip')) { + $ip = $item['modified'] ? $item['modified_ip'].' '.$item['modified'] : $item['created_ip'].' '.$item['created']; + print ',"'.$ip.'"'; } - if ($bean->getAttribute('chinvoice')) print ',"'.str_replace('"','""',$item['invoice']).'"'; + if ($bean->getAttribute('chinvoice')) print ',"'.ttNeutralizeForCsv($item['invoice']).'"'; + if ($bean->getAttribute('chtimesheet')) print ',"'.ttNeutralizeForCsv($item['timesheet_name']).'"'; print "\n"; } } diff --git a/topdf.php b/topdf.php index b1195a4b9..6393d5f55 100644 --- a/topdf.php +++ b/topdf.php @@ -1,40 +1,24 @@ isPluginEnabled('cf')) { require_once('plugins/CustomFields.class.php'); - $custom_fields = new CustomFields($user->team_id); + $custom_fields = new CustomFields(); } // Report settings are stored in session bean before we get here. $bean = new ActionForm('reportBean', new Form('reportForm'), $request); +$config = new ttConfigHelper($user->getConfig()); +$show_note_column = $bean->getAttribute('chnote') && !$config->getDefinedValue('report_note_on_separate_row'); +$show_note_row = $bean->getAttribute('chnote') && $config->getDefinedValue('report_note_on_separate_row'); +$show_cost_per_hour = $config->getDefinedValue('report_cost_per_hour') && ($user->can('manage_invoices') || $user->isClient()); + // There are 2 variations of report: totals only, or normal. Totals only means that the report -// is grouped by either date, user, client, project, task or cf_1 and user only needs to see subtotals by group. +// is grouped by either date, user, client, project, or task and user only needs to see subtotals by group. $totals_only = ($bean->getAttribute('chtotalsonly') == '1'); -// Determine group by header. -$group_by = $bean->getAttribute('group_by'); -if ('no_grouping' != $group_by) { - if ('cf_1' == $group_by) - $group_by_header = $custom_fields->fields[0]['label']; - else { - $key = 'label.'.$group_by; - $group_by_header = $i18n->getKey($key); - } -} - // Obtain items for report. +$options = ttReportHelper::getReportOptions($bean); +$grouping = ttReportHelper::grouping($options); +$items = null; if (!$totals_only) - $items = ttReportHelper::getItems($bean); // Individual entries. -if ($totals_only || 'no_grouping' != $group_by) - $subtotals = ttReportHelper::getSubtotals($bean); // Subtotals for groups of items. -$totals = ttReportHelper::getTotals($bean); // Totals for the entire report. + $items = ttReportHelper::getItems($options); // Individual entries. +if ($totals_only || $grouping) + $subtotals = ttReportHelper::getSubtotals($options); // Subtotals for groups of items. +$totals = ttReportHelper::getTotals($options); // Totals for the entire report. // Assign variables that are used to print subtotals. -if ($items && 'no_grouping' != $group_by) { +$print_subtotals = $first_pass = false; +if ($items && $grouping) { $print_subtotals = true; $first_pass = true; $prev_grouped_by = ''; @@ -88,7 +64,7 @@ } // Build a string to use as filename for the files being downloaded. -$filename = strtolower($i18n->getKey('title.report')).'_'.$bean->mValues['start_date'].'_'.$bean->mValues['end_date']; +$filename = strtolower($i18n->get('title.report')).'_'.$bean->mValues['start_date'].'_'.$bean->mValues['end_date']; // Start preparing HTML to build PDF from. $styleHeader = 'style="background-color:#a6ccf7;"'; @@ -96,19 +72,21 @@ $styleCentered = 'style="text-align:center;"'; $styleRightAligned = 'style="text-align:right;"'; -$title = $i18n->getKey('title.report').": ".$totals['start_date']." - ".$totals['end_date']; +$title = $i18n->get('title.report').": ".$totals['start_date']." - ".$totals['end_date']; $html = '

'.$title.'

'; $html .= ''; if ($totals_only) { // We are building a "totals only" report with only subtotals and total. + $group_by_header = ttReportHelper::makeGroupByHeader($options); $colspan = 1; // Column span for an empty row. // Table header. $html .= ''; $html .= ""; $html .= ''; - if ($bean->getAttribute('chduration')) { $colspan++; $html .= "'; } - if ($bean->getAttribute('chcost')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chduration')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chunits')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chcost')) { $colspan++; $html .= "'; } $html .= ''; $html .= ''; // Print subtotals. @@ -116,9 +94,10 @@ $html .= ''; $html .= ''; if ($bean->getAttribute('chduration')) $html .= "'; + if ($bean->getAttribute('chunits')) $html .= "'; if ($bean->getAttribute('chcost')) { $html .= "'; $html .= ""; - $html .= ''; + $html .= ''; if ($bean->getAttribute('chduration')) $html .= "'; + if ($bean->getAttribute('chunits')) $html .= "'; if ($bean->getAttribute('chcost')) { $html .= "'; $html .= ""; - $html .= ''; - if ($user->canManageTeam() || $user->isClient()) { $colspan++; $html .= ''; } - if ($bean->getAttribute('chclient')) { $colspan++; $html .= ''; } - if ($bean->getAttribute('chproject')) { $colspan++; $html .= ''; } - if ($bean->getAttribute('chtask')) { $colspan++; $html .= ''; } - if ($bean->getAttribute('chcf_1')) { $colspan++; $html .= ''; } - if ($bean->getAttribute('chstart')) { $colspan++; $html .= "'; } - if ($bean->getAttribute('chfinish')) { $colspan++; $html .= "'; } - if ($bean->getAttribute('chduration')) { $colspan++; $html .= "'; } - if ($bean->getAttribute('chnote')) { $colspan++; $html .= ''; } - if ($bean->getAttribute('chcost')) { $colspan++; $html .= "'; } - if ($bean->getAttribute('chinvoice')) { $colspan++; $html .= ''; } + $html .= ''; + if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) { $colspan++; $html .= ''; } + // User custom field labels. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) { $colspan++; $html .= ''; } + } + } + if ($bean->getAttribute('chclient')) { $colspan++; $html .= ''; } + if ($bean->getAttribute('chproject')) { $colspan++; $html .= ''; } + // Project custom field labels. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) { $colspan++; $html .= ''; } + } + } + if ($bean->getAttribute('chtask')) { $colspan++; $html .= ''; } + // Time custom field labels. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) { $colspan++; $html .= ''; } + } + } + if ($bean->getAttribute('chstart')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chfinish')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chduration')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chunits')) { $colspan++; $html .= "'; } + if ($show_note_column) { $colspan++; $html .= ''; } + if ($bean->getAttribute('chcost') && $show_cost_per_hour) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chcost')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chapproved')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chpaid')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chip')) { $colspan++; $html .= "'; } + if ($bean->getAttribute('chinvoice')) { $colspan++; $html .= ''; } + if ($bean->getAttribute('chtimesheet')) { $colspan++; $html .= ''; } $html .= ''; $html .= ''; @@ -170,74 +179,161 @@ $cur_grouped_by = $item['grouped_by']; if ($cur_grouped_by != $prev_grouped_by && !$first_pass) { $html .= ''; - $html .= ''; - if ($user->canManageTeam() || $user->isClient()) { + $html .= ''; + if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) { $html .= ''; } + // User custom fields. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } + } if ($bean->getAttribute('chclient')) { $html .= ''; } if ($bean->getAttribute('chproject')) { $html .= ''; } + // Project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } + } if ($bean->getAttribute('chtask')) { $html .= ''; } - if ($bean->getAttribute('chcf_1')) { - $html .= ''; + // Time custom fields. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } } if ($bean->getAttribute('chstart')) $html .= ''; if ($bean->getAttribute('chfinish')) $html .= ''; if ($bean->getAttribute('chduration')) $html .= "'; - if ($bean->getAttribute('chnote')) $html .= ''; + if ($bean->getAttribute('chunits')) $html .= "'; + if ($show_note_column) $html .= ''; + if ($bean->getAttribute('chcost') && $show_cost_per_hour) $html .= ''; if ($bean->getAttribute('chcost')) { $html .= "'; } + if ($bean->getAttribute('chapproved')) $html .= ''; + if ($bean->getAttribute('chpaid')) $html .= ''; + if ($bean->getAttribute('chip')) $html .= ''; if ($bean->getAttribute('chinvoice')) $html .= ''; + if ($bean->getAttribute('chtimesheet')) $html .= ''; $html .= ''; $html .= ''; + // TODO: page breaks on PDF reports is a rarely used feature. + // Currently without configuration capability. + // Consider adding an option to user profile instead. + if (isTrue('PDF_REPORT_PAGE_BREAKS')) { + import('ttUserConfig'); + $uc = new ttUserConfig(); + $use_breaks = $uc->getValue(SYSC_PDF_REPORT_PAGE_BREAKS); + if ($use_breaks) $html .= '
'; + } } - $first_pass = false; + $first_pass = false; } // Print a regular row. $html .= ''; $html .= ''; - if ($user->canManageTeam() || $user->isClient()) $html .= ''; + if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) $html .= ''; + // User custom fields. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } + } if ($bean->getAttribute('chclient')) $html .= ''; if ($bean->getAttribute('chproject')) $html .= ''; + // Project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } + } if ($bean->getAttribute('chtask')) $html .= ''; - if ($bean->getAttribute('chcf_1')) $html .= ''; + // Time custom fields. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } + } if ($bean->getAttribute('chstart')) $html .= "'; if ($bean->getAttribute('chfinish')) $html .= "'; if ($bean->getAttribute('chduration')) $html .= "'; - if ($bean->getAttribute('chnote')) $html .= ''; + if ($bean->getAttribute('chunits')) $html .= "'; + if ($show_note_column) $html .= ''; + if ($bean->getAttribute('chcost') && $show_cost_per_hour) { + $html .= "'; + } if ($bean->getAttribute('chcost')) { $html .= "'; } + if ($bean->getAttribute('chapproved')) { + $html .= ''; + } + if ($bean->getAttribute('chpaid')) { + $html .= ''; + } + if ($bean->getAttribute('chip')) { + $html .= ''; + } if ($bean->getAttribute('chinvoice')) $html .= ''; + if ($bean->getAttribute('chtimesheet')) $html .= ''; $html .= ''; + if ($show_note_row && $item['note']) { + $html .= ''; + $html .= "'; + $noteSpan = $colspan-1; + $html .= ''; + $html .= ''; + } + $prev_date = $item['date']; if ($print_subtotals) $prev_grouped_by = $item['grouped_by']; } @@ -245,82 +341,136 @@ // Print a terminating subtotal. if ($print_subtotals) { $html .= ''; - $html .= ''; - if ($user->canManageTeam() || $user->isClient()) { + $html .= ''; + if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) { $html .= ''; } + // User custom fields. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } + } if ($bean->getAttribute('chclient')) { $html .= ''; } if ($bean->getAttribute('chproject')) { $html .= ''; } + // Project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } + } if ($bean->getAttribute('chtask')) { $html .= ''; } - if ($bean->getAttribute('chcf_1')) { - $html .= ''; + // Time custom fields. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } } if ($bean->getAttribute('chstart')) $html .= ''; if ($bean->getAttribute('chfinish')) $html .= ''; if ($bean->getAttribute('chduration')) $html .= "'; - if ($bean->getAttribute('chnote')) $html .= ''; + if ($bean->getAttribute('chunits')) $html .= "'; + if ($show_note_column) $html .= ''; + if ($bean->getAttribute('chcost') && $show_cost_per_hour) $html .= ''; if ($bean->getAttribute('chcost')) { $html .= "'; } + if ($bean->getAttribute('chapproved')) $html .= ''; + if ($bean->getAttribute('chpaid')) $html .= ''; + if ($bean->getAttribute('chip')) $html .= ''; if ($bean->getAttribute('chinvoice')) $html .= ''; + if ($bean->getAttribute('chtimesheet')) $html .= ''; $html .= ''; } // Print totals. $html .= ''; $html .= ''; - $html .= ''; - if ($user->canManageTeam() || $user->isClient()) $html .= ''; + $html .= ''; + if ($user->can('view_reports') || $user->can('view_all_reports') || $user->isClient()) $html .= ''; + // User custom fields. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } + } if ($bean->getAttribute('chclient')) $html .= ''; if ($bean->getAttribute('chproject')) $html .= ''; + // Project custom fields. + if (isset($custom_fields) && $custom_fields->projectFields) { + foreach ($custom_fields->projectFields as $projectField) { + $field_name = 'project_field_'.$projectField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } + } if ($bean->getAttribute('chtask')) $html .= ''; - if ($bean->getAttribute('chcf_1')) $html .= ''; + // Time custom fields. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + $checkbox_control_name = 'show_'.$field_name; + if ($bean->getAttribute($checkbox_control_name)) $html .= ''; + } + } if ($bean->getAttribute('chstart')) $html .= ''; if ($bean->getAttribute('chfinish')) $html .= ''; if ($bean->getAttribute('chduration')) $html .= "'; - if ($bean->getAttribute('chnote')) $html .= ''; + if ($bean->getAttribute('chunits')) $html .= "'; + if ($show_note_column) $html .= ''; + if ($bean->getAttribute('chcost') && $show_cost_per_hour) $html .= ''; if ($bean->getAttribute('chcost')) { $html .= "'; } + if ($bean->getAttribute('chapproved')) $html .= ''; + if ($bean->getAttribute('chpaid')) $html .= ''; + if ($bean->getAttribute('chip')) $html .= ''; if ($bean->getAttribute('chinvoice')) $html .= ''; + if ($bean->getAttribute('chtimesheet')) $html .= ''; $html .= ''; $html .= '
'.htmlspecialchars($group_by_header).'".$i18n->getKey('label.duration').'".$i18n->getKey('label.cost').'".$i18n->get('label.duration').'".$i18n->get('label.work_units_short').'".$i18n->get('label.cost').'
'.htmlspecialchars($subtotal['name']).'".$subtotal['time'].'".$subtotal['units'].'"; - if ($user->canManageTeam() || $user->isClient()) + if ($user->can('manage_invoices') || $user->isClient()) $html .= $subtotal['cost']; else $html .= $subtotal['expenses']; @@ -129,12 +108,13 @@ // Print totals. $html .= '
 
'.$i18n->getKey('label.total').''.$i18n->get('label.total').'".$totals['time'].'".$totals['units'].'"; $html .= htmlspecialchars($user->currency).' '; - if ($user->canManageTeam() || $user->isClient()) + if ($user->can('manage_invoices') || $user->isClient()) $html .= $totals['cost']; else $html .= $totals['expenses']; @@ -148,18 +128,47 @@ // Table header. $html .= '
'.$i18n->getKey('label.date').''.$i18n->getKey('label.user').''.$i18n->getKey('label.client').''.$i18n->getKey('label.project').''.$i18n->getKey('label.task').''.htmlspecialchars($custom_fields->fields[0]['label']).'".$i18n->getKey('label.start').'".$i18n->getKey('label.finish').'".$i18n->getKey('label.duration').''.$i18n->getKey('label.note').'".$i18n->getKey('label.cost').''.$i18n->getKey('label.invoice').''.$i18n->get('label.date').''.$i18n->get('label.user').''.htmlspecialchars($userField['label']).''.$i18n->get('label.client').''.$i18n->get('label.project').''.htmlspecialchars($projectField['label']).''.$i18n->get('label.task').''.htmlspecialchars($timeField['label']).'".$i18n->get('label.start').'".$i18n->get('label.finish').'".$i18n->get('label.duration').'".$i18n->get('label.work_units_short').''.$i18n->get('label.note').'".$i18n->get('form.report.per_hour').'".$i18n->get('label.cost').'".$i18n->get('label.approved').'".$i18n->get('label.paid').'".$i18n->get('label.ip').''.$i18n->get('label.invoice').''.$i18n->get('label.timesheet').'
'.$i18n->getKey('label.subtotal').''.$i18n->get('label.subtotal').''; - if ($group_by == 'user') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= htmlspecialchars($subtotals[$prev_grouped_by]['user']); $html .= ''; - if ($group_by == 'client') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= htmlspecialchars($subtotals[$prev_grouped_by]['client']); $html .= ''; - if ($group_by == 'project') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= htmlspecialchars($subtotals[$prev_grouped_by]['project']); $html .= ''; - if ($group_by == 'task') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= htmlspecialchars($subtotals[$prev_grouped_by]['task']); $html .= ''; - if ($group_by == 'cf_1') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); - $html .= '".$subtotals[$prev_grouped_by]['time'].'".$subtotals[$prev_grouped_by]['units'].'"; - if ($user->canManageTeam() || $user->isClient()) + if ($user->can('manage_invoices') || $user->isClient()) $html .= $subtotals[$prev_grouped_by]['cost']; else $html .= $subtotals[$prev_grouped_by]['expenses']; $html .= '
 
'.$item['date'].''.htmlspecialchars($item['user']).''.htmlspecialchars($item['user']).''.htmlspecialchars($item[$field_name]).''.htmlspecialchars($item['client']).''.htmlspecialchars($item['project']).''.htmlspecialchars($item[$field_name]).''.htmlspecialchars($item['task']).''.htmlspecialchars($item['cf_1']).''.htmlspecialchars($item[$field_name]).'".$item['start'].'".$item['finish'].'".$item['duration'].''.htmlspecialchars($item['note']).'".$item['units'].''.htmlspecialchars($item['note']).'"; + $html .= $item['cost_per_hour']; + $html .= '"; - if ($user->canManageTeam() || $user->isClient()) + if ($user->can('manage_invoices') || $user->isClient()) $html .= $item['cost']; else $html .= $item['expense']; $html .= ''; + $html .= $item['approved'] == 1 ? $i18n->get('label.yes') : $i18n->get('label.no'); + $html .= ''; + $html .= $item['paid'] == 1 ? $i18n->get('label.yes') : $i18n->get('label.no'); + $html .= ''; + $html .= $item['modified'] ? $item['modified_ip'].' '.$item['modified'] : $item['created_ip'].' '.$item['created']; + $html .= ''.htmlspecialchars($item['invoice']).''.htmlspecialchars($item['timesheet_name']).'
".$i18n->get('label.note').''.htmlspecialchars($item['note']).'
'.$i18n->getKey('label.subtotal').''.$i18n->get('label.subtotal').''; - if ($group_by == 'user') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= htmlspecialchars($subtotals[$prev_grouped_by]['user']); $html .= ''; - if ($group_by == 'client') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= htmlspecialchars($subtotals[$prev_grouped_by]['client']); $html .= ''; - if ($group_by == 'project') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= htmlspecialchars($subtotals[$prev_grouped_by]['project']); $html .= ''; - if ($group_by == 'task') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); + $html .= htmlspecialchars($subtotals[$prev_grouped_by]['task']); $html .= ''; - if ($group_by == 'cf_1') $html .= htmlspecialchars($subtotals[$prev_grouped_by]['name']); - $html .= '".$subtotals[$prev_grouped_by]['time'].'".$subtotals[$prev_grouped_by]['units'].'"; - if ($user->canManageTeam() || $user->isClient()) + if ($user->can('manage_invoices') || $user->isClient()) $html .= $subtotals[$prev_grouped_by]['cost']; else $html .= $subtotals[$prev_grouped_by]['expenses']; $html .= '
 
'.$i18n->getKey('label.total').''.$i18n->get('label.total').'".$totals['time'].'".$totals['units'].'".htmlspecialchars($user->currency).' '; - if ($user->canManageTeam() || $user->isClient()) + if ($user->can('manage_invoices') || $user->isClient()) $html .= $totals['cost']; else $html .= $totals['expenses']; $html .= '
'; } // Output footer. if (!defined('REPORT_FOOTER') || !(REPORT_FOOTER == false)) // By default we print it unless explicitely defined as false. - $html .= '

'.$i18n->getKey('form.mail.footer').'

'; + $html .= '

'.$i18n->get('form.mail.footer').'

'; // By this time we have html ready. // Determine title for report. -$title = $i18n->getKey('title.report').": ".$totals['start_date']." - ".$totals['end_date']; +$title = $i18n->get('title.report').": ".$totals['start_date']." - ".$totals['end_date']; header('Pragma: public'); // This is needed for IE8 to download files over https. header('Content-Type: text/html; charset=utf-8'); @@ -338,7 +488,7 @@ // Extend TCPDF class so that we can use custom header and footer. class ttPDF extends TCPDF { - public $image_file = 'images/tt_logo.png'; // Image file for the logo in header. + public $image_file = 'img/logo.png'; // Image file for the logo in header. public $page_word = 'Page'; // Localized "Page" word in footer, ex: Page 1/2. // SetImageFile - sets image file name. @@ -372,11 +522,11 @@ public function Footer() { $pdf = new ttPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); // If custom logo file exists - set it. -if (file_exists('images/'.$user->team_id.'.png')) - $pdf->SetImageFile('images/'.$user->team_id.'.png'); +if (file_exists('img/'.$user->group_id.'.png')) + $pdf->SetImageFile('img/'.$user->group_id.'.png'); // Set page word for the footer. -$pdf->SetPageWord($i18n->getKey('label.page')); +$pdf->SetPageWord($i18n->get('label.page')); // Set document information. $pdf->SetCreator(PDF_CREATOR); diff --git a/user_add.php b/user_add.php index a00528bf3..e75e0901d 100644 --- a/user_add.php +++ b/user_add.php @@ -1,51 +1,41 @@ isPluginEnabled('mq'); if ($user->isPluginEnabled('cl')) - $clients = ttTeamHelper::getActiveClients($user->team_id); + $clients = ttGroupHelper::getActiveClients(); +// Use custom fields plugin if it is enabled. +if ($user->isPluginEnabled('cf')) { + require_once('plugins/CustomFields.class.php'); + $custom_fields = new CustomFields(); + $smarty->assign('custom_fields', $custom_fields); +} + +$cl_name = $cl_login = $cl_password1 = $cl_password2 = $cl_email = +$cl_role_id = $cl_client_id = $cl_quota_percent = $cl_rate = null; +$cl_projects = array(); $assigned_projects = array(); if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); @@ -55,50 +45,79 @@ $cl_password2 = $request->getParameter('pas2'); } $cl_email = trim($request->getParameter('email')); - $cl_role = $request->getParameter('role'); - if (!$cl_role) $cl_role = ROLE_USER; + $cl_role_id = $request->getParameter('role'); $cl_client_id = $request->getParameter('client'); + $cl_quota_percent = $request->getParameter('quota_percent'); + // If we have user custom fields - collect input. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $control_name = 'user_field_'.$userField['id']; + $userCustomFields[$userField['id']] = array('field_id' => $userField['id'], + 'control_name' => $control_name, + 'label' => $userField['label'], + 'type' => $userField['type'], + 'required' => $userField['required'], + 'value' => trim($request->getParameter($control_name))); + } + } $cl_rate = $request->getParameter('rate'); $cl_projects = $request->getParameter('projects'); if (is_array($cl_projects)) { foreach ($cl_projects as $p) { if (ttValidFloat($request->getParameter('rate_'.$p), true)) { - $project_with_rate = array(); + $project_with_rate = array(); $project_with_rate['id'] = $p; $project_with_rate['rate'] = $request->getParameter('rate_'.$p); $assigned_projects[] = $project_with_rate; } else - $err->add($i18n->getKey('error.field'), 'rate_'.$p); + $err->add($i18n->get('error.field'), 'rate_'.$p); } } } $form = new Form('userForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 300px;','value'=>$cl_name)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','style'=>'width: 300px;','value'=>$cl_login)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login)); if (!$auth->isPasswordExternal()) { - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas1','aspassword'=>true,'value'=>$cl_password1)); - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas2','aspassword'=>true,'value'=>$cl_password2)); + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'pas1','value'=>$cl_password1)); + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'pas2','value'=>$cl_password2)); } $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','value'=>$cl_email)); -$roles[ROLE_USER] = $i18n->getKey('label.user'); -$roles[ROLE_COMANAGER] = $i18n->getKey('form.users.comanager'); +$active_roles = ttTeamHelper::getActiveRolesForUser(); +$form->addInput(array('type'=>'combobox','onchange'=>'handleClientRole()','name'=>'role','value'=>$cl_role_id,'data'=>$active_roles,'datakeys'=>array('id', 'name'))); if ($user->isPluginEnabled('cl')) - $roles[ROLE_CLIENT] = $i18n->getKey('label.client'); -$form->addInput(array('type'=>'combobox','onchange'=>'handleClientControl()','name'=>'role','value'=>$cl_role,'data'=>$roles)); -if ($user->isPluginEnabled('cl')) - $form->addInput(array('type'=>'combobox','name'=>'client','value'=>$cl_client_id,'data'=>$clients,'datakeys'=>array('id', 'name'),'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + $form->addInput(array('type'=>'combobox','name'=>'client','value'=>$cl_client_id,'data'=>$clients,'datakeys'=>array('id', 'name'),'empty'=>array(''=>$i18n->get('dropdown.select')))); + +// If we have custom fields - add controls for them. +if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + if ($userField['type'] == CustomFields::TYPE_TEXT) { + $form->addInput(array('type'=>'text','name'=>$field_name,'value'=>$userCustomFields[$userField['id']]['value'])); + } elseif ($userField['type'] == CustomFields::TYPE_DROPDOWN) { + $form->addInput(array('type'=>'combobox','name'=>$field_name, + 'data'=>CustomFields::getOptions($userField['id']), + 'value'=>$userCustomFields[$userField['id']]['value'], + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + } + } +} $form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'rate','format'=>'.2','value'=>$cl_rate)); +if ($show_quota) + $form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'quota_percent','format'=>'.2','value'=>$cl_quota_percent)); -$projects = ttTeamHelper::getActiveProjects($user->team_id); +$show_projects = MODE_PROJECTS == $user->getTrackingMode() || MODE_PROJECTS_AND_TASKS == $user->getTrackingMode(); +if ($show_projects) { + $projects = ttGroupHelper::getActiveProjects(); + if (count($projects) == 0) $show_projects = false; +} // Define classes for the projects table. class NameCellRenderer extends DefaultCellRenderer { function render(&$table, $value, $row, $column, $selected = false) { - $this->setOptions(array('width'=>200,'valign'=>'top')); - $this->setValue(''); + $this->setValue(''); return $this->toString(); } } @@ -106,43 +125,56 @@ class RateCellRenderer extends DefaultCellRenderer { function render(&$table, $value, $row, $column, $selected = false) { global $assigned_projects; $field = new FloatField('rate_'.$table->getValueAtName($row, 'id')); + $field->setCssClass('project-rate-field'); $field->setFormName($table->getFormName()); - $field->setLocalization($GLOBALS['I18N']); $field->setSize(5); $field->setFormat('.2'); foreach ($assigned_projects as $p) { if ($p['id'] == $table->getValueAtName($row,'id')) $field->setValue($p['rate']); } - $this->setValue($field->toStringControl()); + $this->setValue($field->getHtml()); return $this->toString(); } } // Create projects table. $table = new Table('projects'); +$table->setCssClass('project-rate-table'); $table->setIAScript('setDefaultRate'); -$table->setTableOptions(array('width'=>'100%','cellspacing'=>'1','cellpadding'=>'3','border'=>'0')); -$table->setRowOptions(array('valign'=>'top','class'=>'tableHeader')); $table->setData($projects); $table->setKeyField('id'); $table->setValue($cl_projects); -$table->addColumn(new TableColumn('name', $i18n->getKey('label.project'), new NameCellRenderer())); -$table->addColumn(new TableColumn('p_rate', $i18n->getKey('form.users.rate'), new RateCellRenderer())); +$table->addColumn(new TableColumn('name', $i18n->get('label.project'), new NameCellRenderer())); +$table->addColumn(new TableColumn('p_rate', $i18n->get('form.users.rate'), new RateCellRenderer())); $form->addInputElement($table); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.submit'))); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.submit'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name')); - if (!ttValidString($cl_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.person_name')); + if (!ttValidString($cl_login)) $err->add($i18n->get('error.field'), $i18n->get('label.login')); if (!$auth->isPasswordExternal()) { - if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); + if (!ttValidString($cl_password1)) $err->add($i18n->get('error.field'), $i18n->get('label.password')); + if (!ttValidString($cl_password2)) $err->add($i18n->get('error.field'), $i18n->get('label.confirm_password')); if ($cl_password1 !== $cl_password2) - $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); + $err->add($i18n->get('error.not_equal'), $i18n->get('label.password'), $i18n->get('label.confirm_password')); + // Check password complexity. + if (!ttCheckPasswordComplexity($cl_password1)) + $err->add($i18n->get('error.weak_password')); } - if (!ttValidEmail($cl_email, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - if (!ttValidFloat($cl_rate, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.users.default_rate')); + if (!ttValidEmail($cl_email, true)) $err->add($i18n->get('error.field'), $i18n->get('label.email')); + // Require selection of a client for a client role. + if ($user->isPluginEnabled('cl') && ttRoleHelper::isClientRole($cl_role_id) && !$cl_client_id) $err->add($i18n->get('error.client')); + if (!ttValidFloat($cl_quota_percent, true)) $err->add($i18n->get('error.field'), $i18n->get('label.quota')); + // Validate input in user custom fields. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($userCustomFields as $userField) { + // Validation is the same for text and dropdown fields. + if (!ttValidString($userField['value'], !$userField['required'])) $err->add($i18n->get('error.field'), htmlspecialchars($userField['label'])); + } + } + if (!ttValidFloat($cl_rate, true)) $err->add($i18n->get('error.field'), $i18n->get('form.users.default_rate')); + if (!ttUserHelper::canAdd()) $err->add($i18n->get('error.user_count')); if ($err->no()) { if (!ttUserHelper::getUserByLogin($cl_login)) { @@ -151,24 +183,42 @@ function render(&$table, $value, $row, $column, $selected = false) { 'login' => $cl_login, 'password' => $cl_password1, 'rate' => $cl_rate, - 'team_id' => $user->team_id, - 'role' => $cl_role, + 'quota_percent' => $cl_quota_percent, + 'group_id' => $user->getGroup(), + 'org_id' => $user->org_id, + 'role_id' => $cl_role_id, 'client_id' => $cl_client_id, 'projects' => $assigned_projects, 'email' => $cl_email); - if (ttUserHelper::insert($fields)) { + $user_id = ttUserHelper::insert($fields); + + // Insert user custom fields if we have them. + $result = true; + if ($user_id && isset($custom_fields) && $custom_fields->userFields) { + $result = $custom_fields->insertEntityFields(CustomFields::ENTITY_USER, $user_id, $userCustomFields); + } + + if ($user_id && $result) { + if (!$user->exists()) { + // We added a user to an empty subgroup. Set new user as on behalf user. + // Needed for user-based things to work (such as notifications config). + $user->setOnBehalfUser($user_id); + } header('Location: users.php'); exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.user_exists')); + $err->add($i18n->get('error.user_exists')); } } // isPost $smarty->assign('auth_external', $auth->isPasswordExternal()); +$smarty->assign('active_roles', $active_roles); $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.userForm.name.focus();handleClientControl();"'); -$smarty->assign('title', $i18n->getKey('title.add_user')); +$smarty->assign('onload', 'onLoad="document.userForm.name.focus();handleClientRole();"'); +$smarty->assign('show_quota', $show_quota); +$smarty->assign('show_projects', $show_projects); +$smarty->assign('title', $i18n->get('title.add_user')); $smarty->assign('content_page_name', 'user_add.tpl'); $smarty->display('index.tpl'); diff --git a/user_delete.php b/user_delete.php index 1f0a40dea..e6ec7365e 100644 --- a/user_delete.php +++ b/user_delete.php @@ -1,71 +1,35 @@ getParameter('id'); +$user_details = $user->getUserDetails($user_id); +if (!$user_details) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. -// Get user id we are deleting from the request. -// A cast to int is for safety against manipulation of request parameter (sql injection). -$user_id = (int) $request->getParameter('id'); - -// We need user name and login to display. -$user_details = ttUserHelper::getUserDetails($user_id); - -// Security checks. -$ok_to_go = $user->canManageTeam(); // Are we authorized for user deletes? -if ($ok_to_go) $ok_to_go = $ok_to_go && $user_details; // Are we deleting a real user? -if ($ok_to_go) $ok_to_go = $ok_to_go && ($user->team_id == $user_details['team_id']); // User belongs to our team? -if ($ok_to_go && $user->isCoManager() && (ROLE_COMANAGER == $user_details['role'])) - $ok_to_go = ($user->id == $user_details['id']); // Comanager is not allowed to delete other comanagers. -if ($ok_to_go && $user->isCoManager() && (ROLE_MANAGER == $user_details['role'])) - $ok_to_go = false; // Comanager is not allowed to delete a manager. - -if (!$ok_to_go) - die ($i18n->getKey('error.sys')); -else - $smarty->assign('user_to_delete', $user_details['name']." (".$user_details['login'].")"); +$smarty->assign('user_to_delete', $user_details['name']." (".$user_details['login'].")"); // Create confirmation form. $form = new Form('userDeleteForm'); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$user_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->getKey('label.delete'))); -$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->getKey('button.cancel'))); +$form->addInput(array('type'=>'submit','name'=>'btn_delete','value'=>$i18n->get('label.delete'))); +$form->addInput(array('type'=>'submit','name'=>'btn_cancel','value'=>$i18n->get('button.cancel'))); if ($request->isPost()) { if ($request->getParameter('btn_delete')) { - if (ttUserHelper::markDeleted($user_id)) { + if ($user->markUserDeleted($user_id)) { // If we deleted the "on behalf" user reset its info in session. if ($user_id == $user->behalf_id) { unset($_SESSION['behalf_id']); @@ -73,9 +37,9 @@ } // If we deleted our own account, do housekeeping and logout. if ($user->id == $user_id) { - // Remove tt_login cookie that stores login name. - unset($_COOKIE['tt_login']); - setcookie('tt_login', NULL, -1); + // Remove LOGIN_COOKIE_NAME cookie that stores login name. + unset($_COOKIE[LOGIN_COOKIE_NAME]); + setcookie(LOGIN_COOKIE_NAME, NULL, -1); $auth->doLogout(); header('Location: login.php'); @@ -84,7 +48,7 @@ } exit(); } else { - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } if ($request->getParameter('btn_cancel')) { @@ -94,6 +58,6 @@ } // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('title', $i18n->getKey('title.delete_user')); +$smarty->assign('title', $i18n->get('title.delete_user')); $smarty->assign('content_page_name', 'user_delete.tpl'); $smarty->display('index.tpl'); diff --git a/user_edit.php b/user_edit.php index ac1143f3a..0633e8a1f 100644 --- a/user_edit.php +++ b/user_edit.php @@ -1,69 +1,50 @@ getParameter('id'); - -// Get user details. -$user_details = ttUserHelper::getUserDetails($user_id); - -// Security checks. -$ok_to_go = $user->canManageTeam(); // Are we authorized for user management? -if ($ok_to_go) $ok_to_go = $ok_to_go && $user_details; // Are we editing a real user? -if ($ok_to_go) $ok_to_go = $ok_to_go && ($user->team_id == $user_details['team_id']); // User belongs to our team? -if ($ok_to_go && $user->isCoManager() && (ROLE_COMANAGER == $user_details['role'])) - $ok_to_go = ($user->id == $user_details['id']); // Comanager is not allowed to edit other comanagers. -if ($ok_to_go && $user->isCoManager() && (ROLE_MANAGER == $user_details['role'])) - $ok_to_go = false; // Comanager is not allowed to edit a manager. -if (!$ok_to_go) { - die ($i18n->getKey('error.sys')); +$user_id = (int)$request->getParameter('id'); +$user_details = $user->getUserDetails($user_id); +if (!$user_details) { + header('Location: access_denied.php'); + exit(); } +// End of access checks. +$show_quota = $user->isPluginEnabled('mq'); if ($user->isPluginEnabled('cl')) - $clients = ttTeamHelper::getActiveClients($user->team_id); + $clients = ttGroupHelper::getActiveClients(); -$projects = ttTeamHelper::getActiveProjects($user->team_id); -$assigned_projects = array(); +// Use custom fields plugin if it is enabled. +if ($user->isPluginEnabled('cf')) { + require_once('plugins/CustomFields.class.php'); + $custom_fields = new CustomFields(); + $smarty->assign('custom_fields', $custom_fields); +} +$show_projects = MODE_PROJECTS == $user->getTrackingMode() || MODE_PROJECTS_AND_TASKS == $user->getTrackingMode(); +$projects = array(); +if ($show_projects) { + $projects = ttGroupHelper::getActiveProjects(); + if (count($projects) == 0) $show_projects = false; +} +$cl_name = $cl_login = $cl_password1 = $cl_password2 = $cl_email = +$cl_role_id = $cl_client_id = $cl_quota_percent = $cl_rate = null; +$assigned_projects = array(); if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_login = trim($request->getParameter('login')); @@ -72,9 +53,22 @@ $cl_password2 = $request->getParameter('pas2'); } $cl_email = trim($request->getParameter('email')); - $cl_role = $request->getParameter('role'); + $cl_role_id = $request->getParameter('role'); $cl_client_id = $request->getParameter('client'); $cl_status = $request->getParameter('status'); + $cl_quota_percent = $request->getParameter('quota_percent'); + // If we have user custom fields - collect input. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $control_name = 'user_field_'.$userField['id']; + $userCustomFields[$userField['id']] = array('field_id' => $userField['id'], + 'control_name' => $control_name, + 'label' => $userField['label'], + 'type' => $userField['type'], + 'required' => $userField['required'], + 'value' => trim($request->getParameter($control_name))); + } + } $cl_rate = $request->getParameter('rate'); $cl_projects = $request->getParameter('projects'); if (is_array($cl_projects)) { @@ -85,15 +79,28 @@ $project_with_rate['rate'] = $request->getParameter('rate_'.$p); $assigned_projects[] = $project_with_rate; } else - $err->add($i18n->getKey('error.field'), 'rate_'.$p); + $err->add($i18n->get('error.field'), 'rate_'.$p); } } } else { $cl_name = $user_details['name']; $cl_login = $user_details['login']; $cl_email = $user_details['email']; - $cl_rate = str_replace('.', $user->decimal_mark, $user_details['rate']); - $cl_role = $user_details['role']; + $cl_quota_percent = str_replace('.', $user->getDecimalMark(), $user_details['quota_percent']); + // If we have user custom fields - collect values from database. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $control_name = 'user_field_'.$userField['id']; + $userCustomFields[$userField['id']] = array('field_id' => $userField['id'], + 'control_name' => $control_name, + 'label' => $userField['label'], + 'type' => $userField['type'], + 'required' => $userField['required'], + 'value' => $custom_fields->getEntityFieldValue(CustomFields::ENTITY_USER, $user_id, $userField['id'], $userField['type'])); + } + } + $cl_rate = str_replace('.', $user->getDecimalMark(), $user_details['rate']); + $cl_role_id = $user_details['role_id']; $cl_client_id = $user_details['client_id']; $cl_status = $user_details['status']; $cl_projects = array(); @@ -104,31 +111,44 @@ } $form = new Form('userForm'); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','style'=>'width: 300px;','value'=>$cl_name)); -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','style'=>'width: 300px;','value'=>$cl_login)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login)); if (!$auth->isPasswordExternal()) { - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas1','aspassword'=>true,'value'=>$cl_password1)); - $form->addInput(array('type'=>'text','maxlength'=>'30','name'=>'pas2','aspassword'=>true,'value'=>$cl_password2)); + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'pas1','value'=>$cl_password1)); + $form->addInput(array('type'=>'password','maxlength'=>'30','name'=>'pas2','value'=>$cl_password2)); } -$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','style'=>'width: 300px;','value'=>$cl_email)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'email','value'=>$cl_email)); -$roles[ROLE_USER] = $i18n->getKey('label.user'); -$roles[ROLE_COMANAGER] = $i18n->getKey('form.users.comanager'); +$active_roles = ttTeamHelper::getActiveRolesForUser(); +$form->addInput(array('type'=>'combobox','onchange'=>'handleClientRole()','name'=>'role','value'=>$cl_role_id,'data'=>$active_roles, 'datakeys'=>array('id', 'name'))); if ($user->isPluginEnabled('cl')) - $roles[ROLE_CLIENT] = $i18n->getKey('label.client'); -$form->addInput(array('type'=>'combobox','onchange'=>'handleClientControl()','name'=>'role','value'=>$cl_role,'data'=>$roles)); -if ($user->isPluginEnabled('cl')) - $form->addInput(array('type'=>'combobox','name'=>'client','value'=>$cl_client_id,'data'=>$clients,'datakeys'=>array('id', 'name'),'empty'=>array(''=>$i18n->getKey('dropdown.select')))); + $form->addInput(array('type'=>'combobox','name'=>'client','value'=>$cl_client_id,'data'=>$clients,'datakeys'=>array('id', 'name'),'empty'=>array(''=>$i18n->get('dropdown.select')))); + +// If we have custom fields - add controls for them. +if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($custom_fields->userFields as $userField) { + $field_name = 'user_field_'.$userField['id']; + if ($userField['type'] == CustomFields::TYPE_TEXT) { + $form->addInput(array('type'=>'text','name'=>$field_name,'value'=>$userCustomFields[$userField['id']]['value'])); + } elseif ($userField['type'] == CustomFields::TYPE_DROPDOWN) { + $form->addInput(array('type'=>'combobox','name'=>$field_name, + 'data'=>CustomFields::getOptions($userField['id']), + 'value'=>$userCustomFields[$userField['id']]['value'], + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + } + } +} $form->addInput(array('type'=>'combobox','name'=>'status','value'=>$cl_status, - 'data'=>array(ACTIVE=>$i18n->getKey('dropdown.status_active'),INACTIVE=>$i18n->getKey('dropdown.status_inactive')))); + 'data'=>array(ACTIVE=>$i18n->get('dropdown.status_active'),INACTIVE=>$i18n->get('dropdown.status_inactive')))); $form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'rate','format'=>'.2','value'=>$cl_rate)); +if ($show_quota) + $form->addInput(array('type'=>'floatfield','maxlength'=>'10','name'=>'quota_percent','format'=>'.2','value'=>$cl_quota_percent)); // Define classes for the projects table. class NameCellRenderer extends DefaultCellRenderer { function render(&$table, $value, $row, $column, $selected = false) { - $this->setOptions(array('width'=>200,'valign'=>'top')); - $this->setValue(''); + $this->setValue(''); return $this->toString(); } } @@ -136,67 +156,86 @@ class RateCellRenderer extends DefaultCellRenderer { function render(&$table, $value, $row, $column, $selected = false) { global $assigned_projects; $field = new FloatField('rate_'.$table->getValueAtName($row,'id')); + $field->setCssClass('project-rate-field'); $field->setFormName($table->getFormName()); - $field->setLocalization($GLOBALS['I18N']); $field->setSize(5); $field->setFormat('.2'); foreach ($assigned_projects as $p) { if ($p['id'] == $table->getValueAtName($row,'id')) $field->setValue($p['rate']); } - $this->setValue($field->toStringControl()); + $this->setValue($field->getHtml()); return $this->toString(); } } // Create projects table. $table = new Table('projects'); +$table->setCssClass('project-rate-table'); $table->setIAScript('setRate'); -$table->setTableOptions(array('width'=>'100%','cellspacing'=>'1','cellpadding'=>'3','border'=>'0')); -$table->setRowOptions(array('valign'=>'top','class'=>'tableHeader')); $table->setData($projects); $table->setKeyField('id'); $table->setValue($cl_projects); -$table->addColumn(new TableColumn('name', $i18n->getKey('label.project'), new NameCellRenderer())); -$table->addColumn(new TableColumn('p_rate', $i18n->getKey('form.users.rate'), new RateCellRenderer())); +$table->addColumn(new TableColumn('name', $i18n->get('label.project'), new NameCellRenderer())); +$table->addColumn(new TableColumn('p_rate', $i18n->get('form.users.rate'), new RateCellRenderer())); $form->addInputElement($table); $form->addInput(array('type'=>'hidden','name'=>'id','value'=>$user_id)); -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.save'))); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.save'))); if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name')); - if (!ttValidString($cl_login)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); + if (!ttValidString($cl_name)) $err->add($i18n->get('error.field'), $i18n->get('label.person_name')); + if (!ttValidString($cl_login)) $err->add($i18n->get('error.field'), $i18n->get('label.login')); if (!$auth->isPasswordExternal() && ($cl_password1 || $cl_password2)) { - if (!ttValidString($cl_password1)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); + if (!ttValidString($cl_password1)) $err->add($i18n->get('error.field'), $i18n->get('label.password')); + if (!ttValidString($cl_password2)) $err->add($i18n->get('error.field'), $i18n->get('label.confirm_password')); if ($cl_password1 !== $cl_password2) - $err->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); + $err->add($i18n->get('error.not_equal'), $i18n->get('label.password'), $i18n->get('label.confirm_password')); + // Check password complexity. + if (!ttCheckPasswordComplexity($cl_password1)) + $err->add($i18n->get('error.weak_password')); } - if (!ttValidEmail($cl_email, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - if (!ttValidFloat($cl_rate, true)) $err->add($i18n->getKey('error.field'), $i18n->getKey('form.users.default_rate')); + if (!ttValidEmail($cl_email, true)) $err->add($i18n->get('error.field'), $i18n->get('label.email')); + // Require selection of a client for a client role. + if ($user->isPluginEnabled('cl') && ttRoleHelper::isClientRole($cl_role_id) && !$cl_client_id) $err->add($i18n->get('error.client')); + if (!ttValidStatus($cl_status)) $err->add($i18n->get('error.field'), $i18n->get('label.status')); + if (!ttValidFloat($cl_quota_percent, true)) $err->add($i18n->get('error.field'), $i18n->get('label.quota')); + // Validate input in user custom fields. + if (isset($custom_fields) && $custom_fields->userFields) { + foreach ($userCustomFields as $userField) { + // Validation is the same for text and dropdown fields. + if (!ttValidString($userField['value'], !$userField['required'])) $err->add($i18n->get('error.field'), htmlspecialchars($userField['label'])); + } + } + if (!ttValidFloat($cl_rate, true)) $err->add($i18n->get('error.field'), $i18n->get('form.users.default_rate')); if ($err->no()) { $existing_user = ttUserHelper::getUserByLogin($cl_login); if (!$existing_user || ($user_id == $existing_user['id'])) { - $fields = array( + $fields = array( 'name' => $cl_name, 'login' => $cl_login, 'password' => $cl_password1, 'email' => $cl_email, 'status' => $cl_status, 'rate' => $cl_rate, + 'quota_percent' => $cl_quota_percent, 'projects' => $assigned_projects); - if (right_assign_roles & $user->rights) { - $fields['role'] = $cl_role; + if (in_array('manage_users', $user->rights) && $cl_role_id) { + $fields['role_id'] = $cl_role_id; $fields['client_id'] = $cl_client_id; } - if (ttUserHelper::update($user_id, $fields)) { + $result = ttUserHelper::update($user_id, $fields); + // Update user custom fields if we have them. + if ($result && isset($custom_fields) && $custom_fields->userFields) { + $result = $custom_fields->updateEntityFields(CustomFields::ENTITY_USER, $user_id, $userCustomFields); + } + if ($result) { // If our own login changed, set new one in cookie to remember it. if (($user_id == $user->id) && ($user->login != $cl_login)) { - setcookie('tt_login', $cl_login, time() + COOKIE_EXPIRE, '/'); + setcookie(LOGIN_COOKIE_NAME, $cl_login, time() + COOKIE_EXPIRE, '/'); } // In case the name of the "on behalf" user has changed - set it in session. @@ -206,9 +245,9 @@ function render(&$table, $value, $row, $column, $selected = false) { // If we deactivated our own account, do housekeeping and logout. if ($user->id == $user_id && !is_null($cl_status) && $cl_status == INACTIVE) { - // Remove tt_login cookie that stores login name. - unset($_COOKIE['tt_login']); - setcookie('tt_login', NULL, -1); + // Remove LOGIN_COOKIE_NAME cookie that stores login name. + unset($_COOKIE[LOGIN_COOKIE_NAME]); + setcookie(LOGIN_COOKIE_NAME, NULL, -1); $auth->doLogout(); header('Location: login.php'); @@ -219,19 +258,30 @@ function render(&$table, $value, $row, $column, $selected = false) { exit(); } else - $err->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $err->add($i18n->getKey('error.user_exists')); + $err->add($i18n->get('error.user_exists')); } } // isPost +$can_swap = false; +if ($user->id == $user_id && $user->can('swap_roles')) { + $users_for_swap = ttTeamHelper::getUsersForSwap(); + if (is_array($users_for_swap) && sizeof($users_for_swap) > 0) + $can_swap = true; +} + $rates = ttProjectHelper::getRates($user_id); $smarty->assign('rates', $rates); $smarty->assign('auth_external', $auth->isPasswordExternal()); +$smarty->assign('active_roles', $active_roles); +$smarty->assign('can_swap', $can_swap); $smarty->assign('forms', array($form->getName()=>$form->toArray())); -$smarty->assign('onload', 'onLoad="document.userForm.name.focus();handleClientControl();"'); +$smarty->assign('onload', 'onLoad="document.userForm.name.focus();handleClientRole();"'); +$smarty->assign('show_quota', $show_quota); +$smarty->assign('show_projects', $show_projects); $smarty->assign('user_id', $user_id); -$smarty->assign('title', $i18n->getKey('title.edit_user')); +$smarty->assign('title', $i18n->get('title.edit_user')); $smarty->assign('content_page_name', 'user_edit.tpl'); $smarty->display('index.tpl'); diff --git a/users.php b/users.php index da5704018..38d4e1bc3 100644 --- a/users.php +++ b/users.php @@ -1,60 +1,48 @@ true)); -if($user->canManageTeam()) { - $can_delete_manager = (1 == count($active_users)); - $inactive_users = ttTeamHelper::getInactiveUsers($user->team_id, true); +// Prepare a list of active users. +$rank = $user->getMaxRankForGroup($user->getGroup()); +if ($user->can('view_users')) + $options = array('status'=>ACTIVE,'include_clients'=>true,'include_login'=>true,'include_role'=>true); +else /* if ($user->can('manage_users')) */ + $options = array('status'=>ACTIVE,'max_rank'=>$rank,'include_clients'=>true,'include_self'=>true,'include_login'=>true,'include_role'=>true); +$active_users = $user->getUsers($options); + +// Prepare a list of inactive users. +if($user->can('manage_users')) { + $options = array('status'=>INACTIVE,'max_rank'=>$rank,'include_clients'=>true,'include_login'=>true,'include_role'=>true); + $inactive_users = $user->getUsers($options); } -// Check if the team is set to show indicators for uncompleted time entries. -if (UNCOMPLETED_INDICATORS == $user->uncompleted_indicators) { +$uncompleted_indicators = $user->getConfigOption('uncompleted_indicators'); +if ($uncompleted_indicators) { + $today = new ttDate(); // Server today. + $todayString = $today->toString(); // Check each active user if they have an uncompleted time entry. - foreach ($active_users as $key => $user) { - $active_users[$key]['has_uncompleted_entry'] = (bool) ttTimeHelper::getUncompleted($user['id']); + foreach ($active_users as $key => $active_user) { + $active_users[$key]['has_uncompleted_entry_today'] = (bool) ttTimeHelper::getFirstUncompletedForDate($active_user['id'], $todayString); + $active_users[$key]['has_uncompleted_entry'] = (bool) ttTimeHelper::getUncompleted($active_user['id']); } + $smarty->assign('uncompleted_indicators', true); } $smarty->assign('active_users', $active_users); $smarty->assign('inactive_users', $inactive_users); -$smarty->assign('can_delete_manager', $can_delete_manager); -$smarty->assign('title', $i18n->getKey('title.users')); +$smarty->assign('show_quota', $user->isPluginEnabled('mq')); +$smarty->assign('title', $i18n->get('title.users')); $smarty->assign('content_page_name', 'users.tpl'); $smarty->display('index.tpl'); diff --git a/week.php b/week.php new file mode 100644 index 000000000..3c2384b91 --- /dev/null +++ b/week.php @@ -0,0 +1,604 @@ +isPluginEnabled('wv')) { + header('Location: feature_disabled.php'); + exit(); +} +if ($user->behalf_id && (!$user->can('track_time') || !$user->checkBehalfId())) { + header('Location: access_denied.php'); // Trying on behalf, but no right or wrong user. + exit(); +} +if (!$user->behalf_id && !$user->can('track_own_time') && !$user->adjustBehalfId()) { + header('Location: access_denied.php'); // Trying as self, but no right for self, and noone to work on behalf. + exit(); +} +if ($request->isPost()) { + $userChanged = (bool)$request->getParameter('user_changed'); // Reused in multiple places below. + if ($userChanged && !($user->can('track_time') && $user->isUserValid((int)$request->getParameter('user')))) { + header('Location: access_denied.php'); // User changed, but no right or wrong user id. + exit(); + } +} +// If we are passed in a date, make sure it is in correct format. +$date = $request->getParameter('date'); +if ($date && !ttValidDbDateFormatDate($date)) { + header('Location: access_denied.php'); + exit(); +} +if ($request->isPost()) { + // Validate that browser_today parameter is in correct format. + $browser_today = $request->getParameter('browser_today'); + if ($browser_today && !ttValidDbDateFormatDate($browser_today)) { + header('Location: access_denied.php'); + exit(); + } +} +// End of access checks. + +// Determine user for whom we display this page. +if ($request->isPost() && $userChanged) { + $user_id = (int)$request->getParameter('user'); + $user->setOnBehalfUser($user_id); +} else { + $user_id = $user->getUser(); +} + +$group_id = $user->getGroup(); + +$showClient = $user->isPluginEnabled('cl'); +$showBillable = $user->isPluginEnabled('iv'); +$trackingMode = $user->getTrackingMode(); +$showProject = MODE_PROJECTS == $trackingMode || MODE_PROJECTS_AND_TASKS == $trackingMode; +$showTask = MODE_PROJECTS_AND_TASKS == $trackingMode; +$taskRequired = false; +if ($showTask) $taskRequired = $user->getConfigOption('task_required'); +$showWeekNote = $user->isOptionEnabled('week_note'); +$showWeekNotes = $user->isOptionEnabled('week_notes'); +$showWeekends = $user->isOptionEnabled('weekends'); +$recordType = $user->getRecordType(); +$showStart = TYPE_START_FINISH == $recordType || TYPE_ALL == $recordType; +$showFiles = $user->isPluginEnabled('at'); +$showRecordCustomFields = $user->isOptionEnabled('record_custom_fields'); + +// Initialize and store date in session. +$cl_date = $request->getParameter('date', @$_SESSION['date']); +$selected_date = new ttDate($cl_date); +if(!$cl_date) + $cl_date = $selected_date->toString(); +$_SESSION['date'] = $cl_date; + +// Determine selected week start and end dates. +$weekStartDay = $user->getWeekStart(); +$t_arr = localtime($selected_date->getTimestamp()); +$t_arr[5] = $t_arr[5] + 1900; +if ($t_arr[6] < $weekStartDay) + $startWeekBias = $weekStartDay - 7; +else + $startWeekBias = $weekStartDay; +$startDate = new ttDate(); +$startDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+$startWeekBias,$t_arr[5])); +$endDate = new ttDate(); +$endDate->setFromUnixTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+6+$startWeekBias,$t_arr[5])); +// The above is needed to set date range (timestring) in page title. + +// Use custom fields plugin if it is enabled. +if ($user->isPluginEnabled('cf')) { + require_once('plugins/CustomFields.class.php'); + $custom_fields = new CustomFields(); + $smarty->assign('custom_fields', $custom_fields); +} + +// Use Monthly Quotas plugin, if applicable. +if ($user->isPluginEnabled('mq')){ + require_once('plugins/MonthlyQuota.class.php'); + $quota = new MonthlyQuota(); + $month_quota_minutes = $quota->getUserQuota($selected_date->year, $selected_date->month); + $month_total = ttTimeHelper::getTimeForMonth($selected_date); + $minutes_left = $month_quota_minutes - ttTimeHelper::toMinutes($month_total); + + $smarty->assign('month_total', $month_total); + $smarty->assign('over_quota', $minutes_left < 0); + $smarty->assign('quota_remaining', ttTimeHelper::toAbsDuration($minutes_left)); +} + +// Initialize variables. +$cl_billable = 1; +if ($showBillable) { + if ($request->isPost()) { + $cl_billable = $request->getParameter('billable'); + $_SESSION['billable'] = (int) $cl_billable; + } else + if (isset($_SESSION['billable'])) + $cl_billable = $_SESSION['billable']; +} +$cl_client = $request->getParameter('client', ($request->isPost() ? null : @$_SESSION['client'])); +$_SESSION['client'] = $cl_client; +$cl_project = $request->getParameter('project', ($request->isPost() ? null : @$_SESSION['project'])); +$_SESSION['project'] = $cl_project; +$cl_task = $request->getParameter('task', ($request->isPost() ? null : @$_SESSION['task'])); +$_SESSION['task'] = $cl_task; +$cl_note = $request->getParameter('comment', ($request->isPost() ? null : @$_SESSION['comment'])); +$_SESSION['comment'] = $cl_note; + +$timeCustomFields = array(); +// If we have time custom fields - collect input. +if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $control_name = 'time_field_'.$timeField['id']; + $cl_control_name = $request->getParameter($control_name, ($request->isPost() ? null : @$_SESSION[$control_name])); + $_SESSION[$control_name] = $cl_control_name; + $timeCustomFields[$timeField['id']] = array('field_id' => $timeField['id'], + 'control_name' => $control_name, + 'label' => $timeField['label'], + 'type' => $timeField['type'], + 'required' => $timeField['required'], + 'value' => trim($cl_control_name)); + } +} + +// Get the data we need to display week view. +// Get column headers, which are day numbers in month. +$dayHeaders = ttWeekViewHelper::getDayHeadersForWeek($startDate->toString()); +$lockedDays = ttWeekViewHelper::getLockedDaysForWeek($startDate->toString()); +// If we are not showing weekends, reduce the above arrays to 5 days only. +$weekend_start_idx = $weekend_end_idx = 0; +if (!$showWeekends) { + if (defined('WEEKEND_START_DAY')) { + $weekend_start_idx = (7 + WEEKEND_START_DAY - $weekStartDay) % 7; + $weekend_end_idx = (7 + WEEKEND_START_DAY + 1 - $weekStartDay) % 7; + } else { + $weekend_start_idx = 6 - $weekStartDay; + $weekend_end_idx = (7 - $weekStartDay) % 7; + } + if ($weekend_end_idx > $weekend_start_idx) { + array_splice($dayHeaders, $weekend_start_idx, 2); + array_splice($lockedDays, $weekend_start_idx, 2); + } else { + array_splice($dayHeaders, $weekend_start_idx, 1); + array_splice($dayHeaders, $weekend_end_idx, 1); + array_splice($lockedDays, $weekend_start_idx, 1); + array_splice($lockedDays, $weekend_end_idx, 1); + } +} + +// Get already existing records. +$records = ttWeekViewHelper::getRecordsForInterval($startDate->toString(), $endDate->toString(), $showFiles); +// Build data array for the table. Format is described in ttWeekViewHelper::getDataForWeekView function. +if ($records) + $dataArray = ttWeekViewHelper::getDataForWeekView($records, $dayHeaders); +else + $dataArray = ttWeekViewHelper::prePopulateFromPastWeeks($startDate->toString(), $dayHeaders); + +// Build day totals (total durations for each day in week). +$dayTotals = ttWeekViewHelper::getDayTotals($dataArray, $dayHeaders); + +// Define rendering class for a label field to the left of durations. +class LabelCellRenderer extends DefaultCellRenderer { + function render(&$table, $value, $row, $column, $selected = false) { + global $user; + $showNotes = $user->isOptionEnabled('week_notes'); + + $this->setOptions(array('width'=>200,'valign'=>'middle')); + + // Special handling for a new week entry (row 0, or 0 and 1 if we show notes). + if (0 == $row) { + $this->setOptions(array('style'=>'text-align: center; font-weight: bold; vertical-align: top;')); + } else if ($showNotes && (1 == $row)) { + $this->setOptions(array('style'=>'text-align: right; vertical-align: top;')); + } else if ($showNotes && (0 != $row % 2)) { + $this->setOptions(array('style'=>'text-align: right;')); + } + // Special handling for not billable entries. + $ignoreRow = $showNotes ? 1 : 0; + if ($row > $ignoreRow) { + $row_id = $table->getValueAtName($row,'row_id'); + $billable = ttWeekViewHelper::parseFromWeekViewRow($row_id, 'bl'); + if (!$billable) { + if (($showNotes && (0 == $row % 2)) || !$showNotes) { + $this->setOptions(array('style'=>'color: red;')); // TODO: style it properly in CSS. + } + } + } + $this->setValue(htmlspecialchars($value)); // This escapes HTML for output. + return $this->toString(); + } +} + +// Define rendering class for a single cell for a time or a comment entry in week view table. +class WeekViewCellRenderer extends DefaultCellRenderer { + function render(&$table, $value, $row, $column, $selected = false) { + global $user; + $showNotes = $user->isOptionEnabled('week_notes'); + + $field_name = $table->getValueAt($row,$column)['control_id']; // Our text field names (and ids) are like x_y (row_column). + $field = new TextField($field_name); + // Disable control if the date is locked. + global $lockedDays; + if ($lockedDays[$column]) + $field->setEnabled(false); + $field->setFormName($table->getFormName()); + $field->setStyle('width: 60px;'); // TODO: need to style everything properly, eventually. + // Provide visual separation for new entry row. + $rowToSeparate = $showNotes ? 1 : 0; + if ($rowToSeparate == $row) { + $field->setStyle('width: 60px; margin-bottom: 40px'); + } + if ($showNotes) { + if (0 == $row % 2) { + $field->setValue($table->getValueAt($row,$column)['duration']); // Duration for even rows. + } else { + $field->setValue($table->getValueAt($row,$column)['note']); // Comment for odd rows. + $field->setTitle(htmlspecialchars($table->getValueAt($row,$column)['note'])); // Tooltip to help view the entire comment. + } + } else { + $field->setValue($table->getValueAt($row,$column)['duration']); + // $field->setTitle($table->getValueAt($row,$column)['note']); // Tooltip to see comment. TODO - value not available. + } + // Disable control when time entry mode is TYPE_START_FINISH and there is no value in control + // because we can't supply start and finish times in week view - there are no fields for them. + if (!$field->getValue() && TYPE_START_FINISH == $user->getRecordType()) { + $field->setEnabled(false); + } + $this->setValue($field->getHtml()); + return $this->toString(); + } +} + +// Elements of weekTimeForm. +$form = new Form('weekTimeForm'); +$largeScreenCalendarRowSpan = 1; // Number of rows calendar spans on large screens. + +if ($user->can('track_time')) { + $rank = $user->getMaxRankForGroup($group_id); + if ($user->can('track_own_time')) + $options = array('status'=>ACTIVE,'max_rank'=>$rank,'include_self'=>true,'self_first'=>true); + else + $options = array('status'=>ACTIVE,'max_rank'=>$rank); + $user_list = $user->getUsers($options); + if (count($user_list) >= 1) { + $form->addInput(array('type'=>'combobox', + 'onchange'=>'document.weekTimeForm.user_changed.value=1;document.weekTimeForm.submit();', + 'name'=>'user', + 'value'=>$user_id, + 'data'=>$user_list, + 'datakeys'=>array('id','name'))); + $form->addInput(array('type'=>'hidden','name'=>'user_changed')); + $largeScreenCalendarRowSpan += 2; + $smarty->assign('user_dropdown', 1); + } +} + +// Create week_durations table. +$table = new Table('week_durations'); +// $table->setCssClass('week_view_table'); // Currently not used. Fix this. +$table->setTableOptions(array('width'=>'100%','cellspacing'=>'1','cellpadding'=>'3','border'=>'0')); +$table->setRowOptions(array('class'=>'tableHeaderCentered')); +$table->setData($dataArray); +// Add columns to table. +$table->addColumn(new TableColumn('label', '', new LabelCellRenderer(), $dayTotals['label'])); + +if ($showWeekends) { + for ($i = 0; $i < 7; $i++) { + $table->addColumn(new TableColumn($dayHeaders[$i], $dayHeaders[$i], new WeekViewCellRenderer(), $dayTotals[$dayHeaders[$i]])); + } +} +else { + for ($i = 0; $i < 5; $i++) { + $table->addColumn(new TableColumn($dayHeaders[$i], $dayHeaders[$i], new WeekViewCellRenderer(), $dayTotals[$dayHeaders[$i]])); + } +} +$table->setInteractive(false); +$form->addInputElement($table); + +// Dropdown for clients in MODE_TIME. Use all active clients. +if (MODE_TIME == $trackingMode && $showClient) { + $active_clients = ttGroupHelper::getActiveClients(true); + $form->addInput(array('type'=>'combobox', + 'onchange'=>'fillProjectDropdown(this.value);', + 'name'=>'client', + 'value'=>$cl_client, + 'data'=>$active_clients, + 'datakeys'=>array('id', 'name'), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; + // Note: in other modes the client list is filtered to relevant clients only. See below. +} + +// Billable checkbox. +if ($showBillable) { + $form->addInput(array('type'=>'checkbox','name'=>'billable','value'=>$cl_billable)); + $largeScreenCalendarRowSpan += 2; +} + +// If we have time custom fields - add controls for them. +if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + if ($timeField['type'] == CustomFields::TYPE_TEXT) { + $form->addInput(array('type'=>'text','name'=>$field_name,'value'=>$timeCustomFields[$timeField['id']]['value'])); + } elseif ($timeField['type'] == CustomFields::TYPE_DROPDOWN) { + $form->addInput(array('type'=>'combobox','name'=>$field_name, + 'data'=>CustomFields::getOptions($timeField['id']), + 'value'=>$timeCustomFields[$timeField['id']]['value'], + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + } + $largeScreenCalendarRowSpan += 2; + } +} + +// If we show project dropdown, add controls for project and client. +$project_list = $client_list = array(); +if ($showProject) { + // Dropdown for projects assigned to user. + $project_list = $user->getAssignedProjects(); + $form->addInput(array('type'=>'combobox', + 'onchange'=>'fillTaskDropdown(this.value);', + 'name'=>'project', + 'value'=>$cl_project, + 'data'=>$project_list, + 'datakeys'=>array('id','name'), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; + + // Dropdown for clients if the clients plugin is enabled. + if ($showClient) { + $active_clients = ttGroupHelper::getActiveClients(true); + // We need an array of assigned project ids to do some trimming. + foreach($project_list as $project) + $projects_assigned_to_user[] = $project['id']; + + // Build a client list out of active clients. Use only clients that are relevant to user. + // Also trim their associated project list to only assigned projects (to user). + foreach($active_clients as $client) { + $projects_assigned_to_client = explode(',', $client['projects']); + if (is_array($projects_assigned_to_client) && is_array($projects_assigned_to_user)) + $intersection = array_intersect($projects_assigned_to_client, $projects_assigned_to_user); + if ($intersection) { + $client['projects'] = implode(',', $intersection); + $client_list[] = $client; + } + } + $form->addInput(array('type'=>'combobox', + 'onchange'=>'fillProjectDropdown(this.value);', + 'name'=>'client', + 'value'=>$cl_client, + 'data'=>$client_list, + 'datakeys'=>array('id', 'name'), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; + } +} + +// Task dropdown. +$task_list = array(); +if ($showTask) { + $task_list = ttGroupHelper::getActiveTasks(); + $form->addInput(array('type'=>'combobox', + 'name'=>'task', + 'value'=>$cl_task, + 'data'=>$task_list, + 'datakeys'=>array('id','name'), + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; +} + +// Week note control. +if ($showWeekNote) { + $form->addInput(array('type'=>'textarea','name'=>'comment','value'=>$cl_note)); + $largeScreenCalendarRowSpan += 2; +} + +// Calendar. +$form->addInput(array('type'=>'calendar','name'=>'date','value'=>$cl_date)); + +// A hidden control for today's date from user's browser. +$form->addInput(array('type'=>'hidden','name'=>'browser_today','value'=>'')); // User current date, which gets filled in on btn_submit click. + +// Submit button. +$form->addInput(array('type'=>'submit','name'=>'btn_submit','onclick'=>'browser_today.value=get_date()','value'=>$i18n->get('button.submit'))); + +// Submit. +if ($request->isPost()) { + if ($request->getParameter('btn_submit')) { + // Validate user input for row 0. + // Determine if a new entry was posted. + $newEntryPosted = false; + foreach($dayHeaders as $dayHeader) { + $control_id = '0_'.$dayHeader; + if ($request->getParameter($control_id)) { + $newEntryPosted = true; + break; + } + } + if ($newEntryPosted) { + if ($showClient && $user->isOptionEnabled('client_required') && !$cl_client) + $err->add($i18n->get('error.client')); + // Validate input in time custom fields. + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($timeCustomFields as $timeField) { + // Validation is the same for text and dropdown fields. + if (!ttValidString($timeField['value'], !$timeField['required'])) $err->add($i18n->get('error.field'), htmlspecialchars($timeField['label'])); + } + } + if ($showProject) { + if (!$cl_project) $err->add($i18n->get('error.project')); + } + if ($showTask && $taskRequired) { + if (!$cl_task) $err->add($i18n->get('error.task')); + } + } + // Finished validating user input for row 0. + + // Process the table of values. + if ($err->no()) { + + // Obtain values. Iterate through posted parameters one by one, + // see if value changed, apply one change at a time until we see an error. + $result = true; + $rowNumber = 0; + // Iterate through existing rows. + foreach ($dataArray as $row) { + // Iterate through days. + foreach ($dayHeaders as $key => $dayHeader) { + // Do not process locked days. + if ($lockedDays[$key]) continue; + // Make control id for the cell. + $control_id = $rowNumber.'_'.$dayHeader; + + // Handle durations and comments in separate blocks of code. + if (!$showWeekNotes || (0 == $rowNumber % 2)) { + // Handle durations row here. + + // Obtain existing and posted durations. + $postedDuration = $request->getParameter($control_id); + $existingDuration = $dataArray[$rowNumber][$dayHeader]['duration']; + // If posted value is not null, check and normalize it. + if ($postedDuration) { + if (false === ttTimeHelper::postedDurationToMinutes($postedDuration)) { + $err->add($i18n->get('error.field'), $i18n->get('label.duration')); + $result = false; break; // Break out. Stop any further processing. + } else { + $minutes = ttTimeHelper::postedDurationToMinutes($postedDuration); + $postedDuration = ttTimeHelper::minutesToDuration($minutes); + } + } + // Do not process if value has not changed. + if ($postedDuration == $existingDuration) + continue; + // Posted value is different. + if ($existingDuration == null) { + // Insert a new record. + $fields = array(); + $fields['row_id'] = $dataArray[$rowNumber]['row_id']; + if (!$fields['row_id']) { + // Special handling for row 0, a new entry. Need to construct new row_id. + $record = array(); + $record['client_id'] = $cl_client; + $record['billable'] = $cl_billable ? '1' : '0'; + $record['project_id'] = $cl_project; + $record['task_id'] = $cl_task; + if (isset($custom_fields) && $custom_fields->timeFields) { + foreach ($custom_fields->timeFields as $timeField) { + $field_name = 'time_field_'.$timeField['id']; + if ($timeField['type'] == CustomFields::TYPE_TEXT) + $record[$field_name] = $timeCustomFields[$timeField['id']]['value']; + else if ($timeField['type'] == CustomFields::TYPE_DROPDOWN) + $record[$field_name.'_option_id'] = $timeCustomFields[$timeField['id']]['value']; + } + } + $fields['row_id'] = ttWeekViewHelper::makeRowIdentifier($record).'_0'; + // Note: no need to check for a possible conflict with an already existing row + // because we are doing an insert that does not affect already existing data. + + if ($showWeekNote) { + $fields['note'] = $request->getParameter('comment'); + } + } + $fields['day_header'] = $dayHeader; + $fields['start_date'] = $startDate->toString(); // To be able to determine date for the entry using $dayHeader. + $fields['duration'] = $postedDuration; + $fields['browser_today'] = $request->getParameter('browser_today', null); + if ($showWeekNotes) { + // Take note value from the control below duration. + $noteRowNumber = $rowNumber + 1; + $note_control_id = $noteRowNumber.'_'.$dayHeader; + if ($request->getParameter($note_control_id)) { + $fields['note'] = $request->getParameter($note_control_id); // This overwrites week note. + } + } + $result = ttWeekViewHelper::insertDurationFromWeekView($fields, $custom_fields, $err); + } elseif ($postedDuration == null) { + // Delete an already existing record here. + $result = ttTimeHelper::delete($dataArray[$rowNumber][$dayHeader]['tt_log_id']); + } else { + $fields = array(); + $fields['tt_log_id'] = $dataArray[$rowNumber][$dayHeader]['tt_log_id']; + $fields['duration'] = $postedDuration; + $result = ttWeekViewHelper::modifyDurationFromWeekView($fields, $err); + } + if (!$result) break; // Break out of the loop in case of first error. + + } else if ($showWeekNotes) { + // Handle commments row here. + + // Obtain existing and posted comments. + $postedComment = $request->getParameter($control_id); + $existingComment = $dataArray[$rowNumber][$dayHeader]['note']; + // If posted value is not null, check it. + if ($postedComment && !ttValidString($postedComment, true)) { + $err->add($i18n->get('error.field'), $i18n->get('label.note')); + $result = false; break; // Break out. Stop any further processing. + } + // Do not process if value has not changed. + if ($postedComment == $existingComment) + continue; + + // Posted value is different. + // TODO: handle new entries separately in the durations block above. + + // Here, only update the comment on an already existing record. + $fields = array(); + $fields['tt_log_id'] = $dataArray[$rowNumber][$dayHeader]['tt_log_id']; + if ($fields['tt_log_id']) { + $fields['comment'] = $postedComment; + $result = ttWeekViewHelper::modifyCommentFromWeekView($fields); + } + if (!$result) break; // Break out of the loop on first error. + } + } + if (!$result) break; // Break out of the loop on first error. + $rowNumber++; + } + if ($result) { + header('Location: week.php'); // Normal exit. + exit(); + } + } + } +} // isPost + +$week_total = ttTimeHelper::getTimeForWeek($selected_date); + +$smarty->assign('large_screen_calendar_row_span', $largeScreenCalendarRowSpan); +$smarty->assign('selected_date', $selected_date); +$smarty->assign('week_total', $week_total); +$smarty->assign('client_list', $client_list); +$smarty->assign('project_list', $project_list); +$smarty->assign('task_list', $task_list); +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="fillDropdowns()"'); +$smarty->assign('timestring', $startDate->toString($user->date_format).' - '.$endDate->toString($user->date_format)); +$smarty->assign('time_records', $records); +$smarty->assign('show_record_custom_fields', $showRecordCustomFields); +$smarty->assign('show_navigation', !$user->isOptionEnabled('week_menu')); +$smarty->assign('show_client', $showClient); +$smarty->assign('show_billable', $showBillable); +$smarty->assign('show_project', $showProject); +$smarty->assign('show_task', $showTask); +$smarty->assign('task_required', $taskRequired); +$smarty->assign('show_week_note', $showWeekNote); +$smarty->assign('show_week_list', $user->isOptionEnabled('week_list')); +$smarty->assign('show_start', $showStart); +$smarty->assign('show_files', $showFiles); +$smarty->assign('title', $i18n->get('menu.week')); +$smarty->assign('content_page_name', 'week.tpl'); +$smarty->display('index.tpl'); diff --git a/week_view.php b/week_view.php new file mode 100644 index 000000000..7db10226a --- /dev/null +++ b/week_view.php @@ -0,0 +1,53 @@ +getConfigHelper(); + +if ($request->isPost()) { + $cl_week_menu = (bool)$request->getParameter('week_menu'); + $cl_week_note = (bool)$request->getParameter('week_note'); + $cl_week_list = (bool)$request->getParameter('week_list'); + $cl_notes = (bool)$request->getParameter('notes'); + $cl_weekends = (bool)$request->getParameter('weekends'); +} else { + $cl_week_menu = $config->getDefinedValue('week_menu'); + $cl_week_note = $config->getDefinedValue('week_note'); + $cl_week_list = $config->getDefinedValue('week_list'); + $cl_notes = $config->getDefinedValue('week_notes'); + $cl_weekends = $config->getDefinedValue('weekends'); +} + +$form = new Form('weekViewForm'); +$form->addInput(array('type'=>'checkbox','name'=>'week_menu','value'=>$cl_week_menu)); +$form->addInput(array('type'=>'checkbox','name'=>'week_note','value'=>$cl_week_note)); +$form->addInput(array('type'=>'checkbox','name'=>'week_list','value'=>$cl_week_list)); +$form->addInput(array('type'=>'checkbox','name'=>'notes','value'=>$cl_notes)); +$form->addInput(array('type'=>'checkbox','name'=>'weekends','value'=>$cl_weekends)); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); + +if ($request->isPost()){ + // Update config. + $config->setDefinedValue('week_menu', $cl_week_menu); + $config->setDefinedValue('week_note', $cl_week_note); + $config->setDefinedValue('week_list', $cl_week_list); + $config->setDefinedValue('week_notes', $cl_notes); + $config->setDefinedValue('weekends', $cl_weekends); + if (!$user->updateGroup(array('config' => $config->getConfig()))) { + $err->add($i18n->get('error.db')); + } +} + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.week_view')); +$smarty->assign('content_page_name', 'week_view.tpl'); +$smarty->display('index.tpl'); diff --git a/work_units.php b/work_units.php new file mode 100644 index 000000000..b8a4a747e --- /dev/null +++ b/work_units.php @@ -0,0 +1,57 @@ +isPluginEnabled('wu')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. + +$config = new ttConfigHelper($user->getConfig()); + +if ($request->isPost()) { + $cl_minutes_in_unit = (int)$request->getParameter('minutes_in_unit'); + $cl_1st_unit_threshold = (int)$request->getParameter('1st_unit_threshold'); + $cl_totals_only = (bool)$request->getParameter('totals_only'); +} else { + $cl_minutes_in_unit = $user->getConfigInt('minutes_in_unit', 15); + $cl_1st_unit_threshold = $user->getConfigInt('1st_unit_threshold', 0); + $cl_totals_only = $user->getConfigOption('unit_totals_only'); +} + +$form = new Form('workUnitsForm'); +$form->addInput(array('type'=>'text','class'=>'short-text-field','name'=>'minutes_in_unit', 'value'=>$cl_minutes_in_unit)); +$form->addInput(array('type'=>'text','class'=>'short-text-field','name'=>'1st_unit_threshold', 'value'=>$cl_1st_unit_threshold)); +$form->addInput(array('type'=>'checkbox','name'=>'totals_only','value'=>$cl_totals_only)); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); + +if ($request->isPost()){ + // Validate user input. + if (!ttValidInteger($cl_minutes_in_unit) || $cl_minutes_in_unit == 0) $err->add($i18n->get('error.field'), $i18n->get('form.work_units.minutes_in_unit')); + if (!ttValidInteger($cl_1st_unit_threshold, true) ||($cl_minutes_in_unit && $cl_1st_unit_threshold > $cl_minutes_in_unit)) $err->add($i18n->get('error.field'), $i18n->get('form.work_units.1st_unit_threshold')); + // Finished validating user input. + + if ($err->no()) { + $config->setIntValue('minutes_in_unit', $cl_minutes_in_unit); + $config->setIntValue('1st_unit_threshold', $cl_1st_unit_threshold); + $config->setDefinedValue('unit_totals_only', $cl_totals_only); + if (!$user->updateGroup(array('config' => $config->getConfig()))) { + $err->add($i18n->get('error.db')); + } + } +} + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('title', $i18n->get('title.work_units')); +$smarty->assign('content_page_name', 'work_units.tpl'); +$smarty->display('index.tpl');