diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..697c76fe8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +trim_trailing_whitespace = false +insert_new_final_line = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d81178e11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +config.php +WEB-INF/templates_c/*.* +WEB-INF/templates_c/import_* +WEB-INF/templates_c/tt* +WEB-INF/lib/tcpdf/ +nbproject/ +upload/ +.vscode/ +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 5de442b76..022c0c030 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ -# timetracker -Simple, easy to use time tracking system. +# Anuko Time Tracker + +## About +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 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 +* Forum: https://www.anuko.com/forum/viewforum.php?f=4 +* 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 3ab235789..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. @@ -137,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. // @@ -168,11 +116,16 @@ define('LANG_DEFAULT', ''); define('CURRENCY_DEFAULT', '$'); -// EXPORT_DECIMAL_DURATION - defines whether time duration values are decimal in CSV and XML data exports (1.25 vs 1:15). +// EXPORT_DECIMAL_DURATION - defines whether time duration values are decimal in CSV and XML data exports (1.25 or 1,25 vs 1:15). // define('EXPORT_DECIMAL_DURATION', true); +// REPORT_FOOTER - defines whether to use a footer on reports. +// +define('REPORT_FOOTER', true); + + // Authentication module (see WEB-INF/lib/auth/) // Possible authentication methods: // db - internal database, logins and password hashes are stored in time tracker database. @@ -180,27 +133,50 @@ define('EXPORT_DECIMAL_DURATION', 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. + + +// 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'); + + +// 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. + -// 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. -?> \ No newline at end of file +// 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 243eed567..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(); @@ -52,18 +53,18 @@ function authenticate($login, $password) { return false; } - + // isPasswordExternal - returns true if actual password is not stored in the internal DB. function isPasswordExternal() { return false; } - + // doLogin - perfoms a login procedure. 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,17 +77,17 @@ 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; } - + $this->setAuth($val['id'], $login); return true; } @@ -111,7 +112,7 @@ function setAuth($userid, $username) { function getUserLogin() { return $_SESSION['login']; } - + // getUserId - retrieves user ID from session. function getUserId() { if (isset($_SESSION['authenticated_user_id'])) @@ -132,4 +133,3 @@ static function &factory($module, $params = array()) } } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/DateAndTime.class.php b/WEB-INF/lib/DateAndTime.class.php deleted file mode 100644 index 26a51c582..000000000 --- a/WEB-INF/lib/DateAndTime.class.php +++ /dev/null @@ -1,365 +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 DateAndTime($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)); - } - } -} -?> \ No newline at end of file diff --git a/WEB-INF/lib/I18n.class.php b/WEB-INF/lib/I18n.class.php index 96c374381..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,136 +32,176 @@ 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, "."); - if (!($pos === false)) { - $p = explode(".", $kword); - $str = ""; - foreach ($p as $w) { - $str .= "[\"".$w."\"]"; - } - //$value = addslashes($value); - eval("\$this->keys".$str."='".$value."';"); - } else { - $this->keys[$kword] = $value; - } + $pos = strpos($kword, "."); + if (!($pos === false)) { + $p = explode(".", $kword); + $str = ""; + foreach ($p as $w) { + $str .= "[\"".$w."\"]"; + } + eval("\$this->keys".$str."='".$value."';"); + } else { + $this->keys[$kword] = $value; + } } } - $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, "."); - if (!($pos === false)) { - $p = explode(".", $kword); - $str = ""; - foreach ($p as $w) { - $str .= "[\"".$w."\"]"; - } - //$value = addslashes($value); - eval("\$this->keys".$str."='".$value."';"); - } else { - $this->keys[$kword] = $value; - } + if (!$value) continue; + $pos = strpos($kword, "."); + if (!($pos === false)) { + $p = explode(".", $kword); + $str = ""; + foreach ($p as $w) { + $str .= "[\"".$w."\"]"; + } + eval("\$this->keys".$str."='".$value."';"); + } else { + $this->keys[$kword] = $value; + } + } + } + + // 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; + } } - return true; } } + // 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'; return file_exists($filename); } + // getBrowserLanguage() returns a first supported language from browser settings. function getBrowserLanguage() { $acclang = @$_SERVER['HTTP_ACCEPT_LANGUAGE']; if (empty($acclang)) { - return ""; + return false; } $lang_prefs = explode(',', $acclang); foreach ($lang_prefs as $lang_pref) { $lang_pref_parts = explode(';', trim($lang_pref)); - $lang_parts = explode('-', trim($lang_pref_parts[0])); + $lang = $lang_pref_parts[0]; + if ($this->hasLang($lang)) { + return $lang; // Return full language designation (if available), such as pt-BR. + } + + if (strlen($lang) <= 2) + continue; // Do not bother determining main language because we already have it. + + $lang_parts = explode('-', trim($lang)); $lang_main = $lang_parts[0]; - if ($this->hasLang($lang_main)) { - return $lang_main; + if ($lang_main != $lang && $this->hasLang($lang_main)) { + return $lang_main; // Return main language designation, such as pt. } } - return ""; + 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); while (($file = @readdir($d))) { if (($file != ".") && ($file != "..")) { - if (strpos($file, ".lang.php")) { - $fileList[] = @basename($file); - } + if (strpos($file, ".lang.php")) { + $fileList[] = @basename($file); + } } } @closedir($d); 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]; + } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/Period.class.php b/WEB-INF/lib/Period.class.php deleted file mode 100644 index 1880cf15a..000000000 --- a/WEB-INF/lib/Period.class.php +++ /dev/null @@ -1,148 +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; - } -} -?> \ No newline at end of file diff --git a/WEB-INF/lib/PieChartEx.class.php b/WEB-INF/lib/PieChartEx.class.php index 6f482b554..20adfd7d5 100644 --- a/WEB-INF/lib/PieChartEx.class.php +++ b/WEB-INF/lib/PieChartEx.class.php @@ -46,4 +46,4 @@ public function renderEx($options) $this->plot->render($options['fileName']); } } -?> + diff --git a/WEB-INF/lib/auth/Auth_db.class.php b/WEB-INF/lib/auth/Auth_db.class.php index 7475e6bcb..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"; + $mdb2 = getConnection(); + + // Try md5 password match first. + $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,32 +29,51 @@ 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']); } - return false; } + + // Special handling for admin@localhost - search for an account with admin role with a matching password. + if ($login == 'admin@localhost') { + $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 (isset($val['id']) && $val['id'] > 0) { + return array('login'=>$val['login'],'id'=>$val['id']); + } + } + + return false; } function isPasswordExternal() { diff --git a/WEB-INF/lib/auth/Auth_ldap.class.php b/WEB-INF/lib/auth/Auth_ldap.class.php index 93fdebf4a..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. /** @@ -51,12 +23,11 @@ class Auth_ldap extends Auth { var $params; - function Auth_ldap($params) + 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; } @@ -78,6 +49,13 @@ function ldap_escape($str){ */ function authenticate($login, $password) { + // Special handling for admin@localhost - authenticate against db, not ldap. + // It is a fallback mechanism when admin account in LDAP directory does not exist or is misconfigured. + if ($login == 'admin@localhost') { + import('auth.Auth_db'); + return Auth_db::authenticate($login, $password); + } + if (!function_exists('ldap_bind')) { die ('php_ldap extension not loaded!'); } @@ -90,37 +68,46 @@ 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 '
'; } if (!$lc) return false; - + 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') { - - // check if the user specified full login + if ($this->params['type'] == 'ad') { + + // 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 '
'; } @@ -130,19 +117,18 @@ function authenticate($login, $password) return false; } - if ($member_of) { - // get groups + if ($member_of) { + // 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; @@ -150,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 '
'; } @@ -162,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; @@ -184,31 +169,28 @@ function authenticate($login, $password) } ldap_unbind($lc); + return array('login' => $login, 'data' => $entries, 'member_of' => $groups); + } - // handle special case - admin account, strip domain part - if (strpos($login, 'admin@') !== false) { - $login = substr($login, 0, 5); - } + if ($this->params['type'] == 'openldap') { - return array('login' => $login, 'data' => $entries, 'member_of' => $groups); - } else { - // 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 '
'; } @@ -218,14 +200,19 @@ function authenticate($login, $password) return false; } - if ($member_of) { - // get groups + if ($member_of) { - $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 '
'; } @@ -238,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 '
'; } @@ -248,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); + $grp = $entries[0]['memberof'][$i]; + $groups[] = $grp; // append group to array + if (isTrue('DEBUG')) { + var_dump($grp); echo ' appended to $groups
'; + } } - if (defined('AUTH_DEBUG') && isTrue(AUTH_DEBUG)) { - echo '$member_of'; var_dump($member_of); echo '
'; - }; - // 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; } } @@ -273,16 +262,20 @@ function authenticate($login, $password) ldap_unbind($lc); - // handle special case - admin account, strip domain part - if (strpos($login, 'admin@') !== false) { - $login = substr($login, 0, 5); + // 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() { return true; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/common.lib.php b/WEB-INF/lib/common.lib.php index 4111c3e15..79aba106b 100644 --- a/WEB-INF/lib/common.lib.php +++ b/WEB-INF/lib/common.lib.php @@ -1,42 +1,13 @@ load_class: error loading file "'.$filename.'"'; die(); - } +} // The mu_sort function is used to sort a multi-dimensional array. // It looks like the code example is taken from the PHP manual http://ca2.php.net/manual/en/function.sort.php @@ -110,13 +81,6 @@ function toFloat($value) { return null; } - function stripslashes_deep($value) { - $value = is_array($value) ? - array_map('stripslashes_deep', $value) : - stripslashes($value); - return $value; - } - function &getConnection() { if (!isset($GLOBALS["_MDB2_CONNECTION"])) { @@ -127,7 +91,6 @@ function &getConnection() { die($mdb2->getMessage()); } - $mdb2->setOption('debug', true); $mdb2->setFetchMode(MDB2_FETCHMODE_ASSOC); $GLOBALS["_MDB2_CONNECTION"] = $mdb2; @@ -136,36 +99,17 @@ function &getConnection() { } - function closeConnection() { - if (isset($GLOBALS["_DB_CONNECTION"])) { - $GLOBALS["_DB_CONNECTION"]->close(); - unset($GLOBALS["_DB_CONNECTION"]); - } - } - -function time_to_decimal($a) { - $tmp = explode(":", $a); - if($tmp[1]{0}=="0") $tmp[1] = $tmp[1]{1}; +// time_to_decimal converts a time string such as 1:15 to its decimal representation such as 1.25 or 1,25. +function time_to_decimal($val) { + global $user; + $parts = explode(':', $val); // parts[0] is hours, parts[1] is minutes. - $m = round($tmp[1]*100/60); + $minutePercent = round($parts[1]*100/60); // Integer value (0-98) of percent of minutes portion in the hour. + if($minutePercent < 10) $minutePercent = '0'.$minutePercent; // Pad small values with a 0 to always have 2 digits. - if($m<10) $m = "0".$m; - $time = $tmp[0].".".$m; - return $time; -} - -function sec_to_time_fmt_hm($sec) -{ - return sprintf("%d:%02d", $sec / 3600, $sec % 3600 / 60); -} + $decimalTime = $parts[0].$user->decimal_mark.$minutePercent; // Construct decimal representation of time value. -function magic_quotes_off() -{ - // if (get_magic_quotes_gpc()) { // This check is now done before calling this function. - $_POST = array_map('stripslashes_deep', $_POST); - $_GET = array_map('stripslashes_deep', $_GET); - $_COOKIE = array_map('stripslashes_deep', $_COOKIE); - // } + return $decimalTime; } // check_extension checks whether a required PHP extension is loaded and dies if not so. @@ -178,23 +122,99 @@ function check_extension($ext) // isTrue is a helper function to return correct false for older config.php values defined as a string 'false'. function isTrue($val) { - return ($val == false || $val === 'false') ? false : true; + return (defined($val) && constant($val) === true); } // ttValidString is used to check user input to validate a string. -function ttValidString($val, $emptyValid = false) +function ttValidString($val, $emptyValid = false, $maxChars = 0) { + if (is_null($val)) { + return $emptyValid ? true : false; + } + $val = trim($val); if (strlen($val) == 0 && !$emptyValid) return false; - + // String must not be XSS evil (to insert JavaScript). if (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; + } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/Checkbox.class.php b/WEB-INF/lib/form/Checkbox.class.php index f605edf65..619d39fb3 100644 --- a/WEB-INF/lib/form/Checkbox.class.php +++ b/WEB-INF/lib/form/Checkbox.class.php @@ -23,56 +23,44 @@ // | // +----------------------------------------------------------------------+ // | 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 Checkbox($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"; return $html; } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/CheckboxCellRenderer.class.php b/WEB-INF/lib/form/CheckboxCellRenderer.class.php index 5c3180ed2..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()."\""; } @@ -44,4 +44,3 @@ function render(&$table, $value, $row, $column, $selected = false) { return $html; } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/CheckboxGroup.class.php b/WEB-INF/lib/form/CheckboxGroup.class.php index 6fef23171..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 CheckboxGroup($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."\"> - - - - -
\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"; @@ -223,4 +248,3 @@ function _addJavaScript() { return $html; } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/TableColumn.class.php b/WEB-INF/lib/form/TableColumn.class.php index 3af7b2a43..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 TableColumn($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 TableColumn($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; } @@ -79,4 +86,3 @@ function getWidth() { return $this->mWidth; } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/TextArea.class.php b/WEB-INF/lib/form/TextArea.class.php index 9cfd43158..4faac1447 100644 --- a/WEB-INF/lib/form/TextArea.class.php +++ b/WEB-INF/lib/form/TextArea.class.php @@ -23,82 +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 TextArea($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; - } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/TextField.class.php b/WEB-INF/lib/form/TextField.class.php index 3d5ce7af7..bfc0e551f 100644 --- a/WEB-INF/lib/form/TextField.class.php +++ b/WEB-INF/lib/form/TextField.class.php @@ -23,55 +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 TextField($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; + } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/UploadFile.class.php b/WEB-INF/lib/form/UploadFile.class.php index be57fa4c4..36d0d92a7 100644 --- a/WEB-INF/lib/form/UploadFile.class.php +++ b/WEB-INF/lib/form/UploadFile.class.php @@ -23,47 +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 UploadFile($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; + } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/html/HttpRequest.class.php b/WEB-INF/lib/html/HttpRequest.class.php index 857194a15..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 { @@ -31,21 +31,30 @@ class ttHttpRequest { function getMethod() { return isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : false; } - - // The getParameter is the primary function of this class. It returns request parameter, + + // The isGet function determines if a request method is a GET. + function isGet() { + return ($this->getMethod() == 'GET'); + } + + // The isPost function determines if a request method is a POST. + function isPost() { + return ($this->getMethod() == 'POST'); + } + + // 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; } } -?> \ No newline at end of file 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 1b84e745d..4c7eb0134 100644 --- a/WEB-INF/lib/mail/Mailer.class.php +++ b/WEB-INF/lib/mail/Mailer.class.php @@ -1,122 +1,136 @@ mSendType = $type; + var $mMailMode; + var $mCharSet = 'iso-8859-1'; + var $mContentType = 'text/plain'; + var $mSender; + var $mReceiver; + var $mReceiverCC; + var $mReceiverBCC; + + function __construct($type='mail') { + $this->mMailMode = $type; + } + + function setMailMode($value) { + $this->mMailMode = $value; + } + + function setCharSet($value) { + $this->mCharSet = $value; + } + + function setContentType($value) { + $this->mContentType = $value; + } + + function setReceiver($value) { + $this->mReceiver = $value; + } + + function setReceiverCC($value) { + $this->mReceiverCC = $value; + } + + function setReceiverBCC($value) { + $this->mReceiverBCC = $value; + } + + function setSender($value) { + $this->mSender = $value; + } + + function send($subject, $data) { + $data = chunk_split(base64_encode($data)); + $subject = Mailer::mimeEncode($subject, $this->mCharSet); + + $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', + 'Content-Type' => $this->mContentType.'; charset='.$this->mCharSet, + 'Content-Transfer-Encoding' => 'BASE64')); + + // PEAR::Mail + require_once('Mail.php'); + + $recipients = $this->mReceiver; + switch ($this->mMailMode) { + case 'mail': + $mail = Mail::factory('mail'); + break; + + case 'smtp': + // 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 = isTrue('MAIL_SMTP_AUTH'); + $debug = isTrue('MAIL_SMTP_DEBUG'); + + $mail = Mail::factory('smtp', array ('host' => $host, + 'port' => $port, + 'username' => $username, + 'password' => $password, + 'auth' => $auth, + 'debug' => $debug)); + break; } - function setSendType($value) { - $this->mSendType = $value; - } - - function setCharSet($value) { - $this->mCharSet = $value; - } + if (isTrue('MAIL_SMTP_DEBUG')) + PEAR::setErrorHandling(PEAR_ERROR_PRINT); - function setContentType($value) { - $this->mContentType = $value; - } - - function setReceiver($value) { - $this->mReceiver = $value; - } + $res = $mail->send($recipients, $headers, $data); + return (!is_a($res, 'PEAR_Error')); + } - function setReceiverCC($value) { - $this->mReceiverCC = $value; + function mimeEncode($in_str, $charset) { + $out_str = $in_str; + if ($out_str && $charset) { + $start = '=?'.strtoupper($charset).'?B?'; + $end = '?='; + $out_str = base64_encode($out_str); + $out_str = $start . $out_str . $end; } - - function setSender($value) { - $this->mSender = $value; - } - - function send($subject, $data) { - $data = chunk_split(base64_encode($data)); - $subject = Mailer::mimeEncode($subject, $this->mCharSet); - - $headers = array( - 'From' => $this->mSender, - 'To' => $this->mReceiver); - if (isset($this->mReceiverCC)) $headers = array_merge($headers, array( - 'CC' => $this->mReceiverCC)); - $headers = array_merge($headers, array( - 'Subject' => $subject, - 'MIME-Version' => '1.0', - 'Content-Type' => $this->mContentType.'; charset='.$this->mCharSet, - 'Content-Transfer-Encoding' => 'BASE64', - )); - - // PEAR::Mail - require_once('Mail.php'); - - $recipients = $this->mReceiver; - switch ($this->mSendType) { - case 'mail': - $mail = Mail::factory('mail'); - break; - - case "smtp": - // Mail_smtp does not do CC -> recipients conversion - if (!empty($this->mReceiverCC)) { - // make exactly one space after a comma - $recipients .= ', ' . preg_replace('/,[[:space:]]+/', ', ', $this->mReceiverCC);; - } - - $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; - - $mail = Mail::factory('smtp', array ('host' => $host, - 'port' => $port, - 'username' => $username, - 'password' => $password, - 'auth' => $auth, - 'debug' => $debug)); - break; - } - - if (defined('MAIL_SMTP_DEBUG') && isTrue(MAIL_SMTP_DEBUG)) - PEAR::setErrorHandling(PEAR_ERROR_PRINT); - $res = $mail->send($recipients, $headers, $data); - return (!is_a($res, 'PEAR_Error')); - } - - /** - * convert to base64-string - * - * @param string $in_str - * @param string $charset - * @return string - */ - function mimeEncode($in_str, $charset) { - $out_str = $in_str; - if ($out_str && $charset) { - - $end = "?="; - $start = "=?" . strtoupper($charset) . "?B?"; - $spacer = $end . "\r\n " . $start; - - $length = 75 - strlen($start) - strlen($end); - $length = floor($length/2) * 2; - - $out_str = base64_encode($out_str); - //$out_str = Mail::encodemime($out_str,"base64"); - //$out_str = chunk_split($out_str, $length, $spacer); - - //$spacer = preg_quote($spacer); - //$out_str = preg_replace("/" . $spacer . "$/", "", $out_str); - $out_str = $start . $out_str . $end; - } - return $out_str; - } -} \ No newline at end of file + return $out_str; + } +} diff --git a/WEB-INF/lib/pear/INSTALL b/WEB-INF/lib/pear/INSTALL index 08f4c0b08..1312bc888 100644 --- a/WEB-INF/lib/pear/INSTALL +++ b/WEB-INF/lib/pear/INSTALL @@ -50,5 +50,3 @@ a public mailing list devoted to support for PEAR packages and installation- related issues. Happy PHPing, we hope PEAR will be a great tool for your development work! - -$Id: INSTALL 313023 2011-07-06 19:17:11Z dufuz $ \ No newline at end of file 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/pear/Mail.php b/WEB-INF/lib/pear/Mail.php index 75132ac2a..e7cff2f64 100644 --- a/WEB-INF/lib/pear/Mail.php +++ b/WEB-INF/lib/pear/Mail.php @@ -1,8 +1,8 @@ * @copyright 1997-2010 Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Mail.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @version CVS: $Id$ * @link http://pear.php.net/package/Mail/ */ @@ -50,8 +50,7 @@ * mailers under the PEAR hierarchy, and provides supporting functions * useful in multiple mailer backends. * - * @access public - * @version $Revision: 294747 $ + * @version $Revision$ * @package Mail */ class Mail @@ -60,7 +59,7 @@ class Mail * Line terminator used for separating header lines. * @var string */ - var $sep = "\r\n"; + public $sep = "\r\n"; /** * Provides an interface for generating Mail:: objects of various @@ -68,10 +67,10 @@ class Mail * * @param string $driver The kind of Mail:: object to instantiate. * @param array $params The parameters to pass to the Mail:: object. + * * @return object Mail a instance of the driver class or if fails a PEAR Error - * @access public */ - function &factory($driver, $params = array()) + public static function factory($driver, $params = array()) { $driver = strtolower($driver); @include_once 'Mail/' . $driver . '.php'; @@ -108,10 +107,9 @@ function &factory($driver, $params = array()) * containing a descriptive error message on * failure. * - * @access public * @deprecated use Mail_mail::send instead */ - function send($recipients, $headers, $body) + public function send($recipients, $headers, $body) { if (!is_array($headers)) { return PEAR::raiseError('$headers must be an array'); @@ -147,10 +145,8 @@ function send($recipients, $headers, $body) * filter is to prevent mail injection attacks. * * @param array $headers The associative array of headers to sanitize. - * - * @access private */ - function _sanitizeHeaders(&$headers) + protected function _sanitizeHeaders(&$headers) { foreach ($headers as $key => $value) { $headers[$key] = @@ -173,9 +169,8 @@ function _sanitizeHeaders(&$headers) * otherwise returns an array containing two * elements: Any From: address found in the headers, * and the plain text version of the headers. - * @access private */ - function prepareHeaders($headers) + protected function prepareHeaders($headers) { $lines = array(); $from = null; @@ -235,9 +230,8 @@ function prepareHeaders($headers) * * @return mixed An array of forward paths (bare addresses) or a PEAR_Error * object if the address list could not be parsed. - * @access private */ - function parseRecipients($recipients) + protected function parseRecipients($recipients) { include_once 'Mail/RFC822.php'; @@ -250,7 +244,8 @@ function parseRecipients($recipients) // Parse recipients, leaving out all personal info. This is // for smtp recipients, etc. All relevant personal information // should already be in the headers. - $addresses = Mail_RFC822::parseAddressList($recipients, 'localhost', false); + $Mail_RFC822 = new Mail_RFC822(); + $addresses = $Mail_RFC822->parseAddressList($recipients, 'localhost', false); // If parseAddressList() returned a PEAR_Error object, just return it. if (is_a($addresses, 'PEAR_Error')) { diff --git a/WEB-INF/lib/pear/Mail/RFC822.php b/WEB-INF/lib/pear/Mail/RFC822.php index 58d36465c..d010a20e8 100644 --- a/WEB-INF/lib/pear/Mail/RFC822.php +++ b/WEB-INF/lib/pear/Mail/RFC822.php @@ -2,7 +2,7 @@ /** * RFC 822 Email address list validation Utility * - * PHP versions 4 and 5 + * PHP version 5 * * LICENSE: * @@ -40,7 +40,7 @@ * @author Chuck Hagenbuch * @author Chuck Hagenbuch - * @version $Revision: 294749 $ + * @version $Revision$ * @license BSD * @package Mail */ @@ -141,7 +141,6 @@ class Mail_RFC822 { * Sets up the object. The address must either be set here or when * calling parseAddressList(). One or the other. * - * @access public * @param string $address The address(es) to validate. * @param string $default_domain Default domain/host etc. If not supplied, will be set to localhost. * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing. @@ -149,7 +148,7 @@ class Mail_RFC822 { * * @return object Mail_RFC822 A new Mail_RFC822 object. */ - function Mail_RFC822($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) + public function __construct($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) { if (isset($address)) $this->address = $address; if (isset($default_domain)) $this->default_domain = $default_domain; @@ -162,7 +161,6 @@ function Mail_RFC822($address = null, $default_domain = null, $nest_groups = nul * Starts the whole process. The address must either be set here * or when creating the object. One or the other. * - * @access public * @param string $address The address(es) to validate. * @param string $default_domain Default domain/host etc. * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing. @@ -170,7 +168,7 @@ function Mail_RFC822($address = null, $default_domain = null, $nest_groups = nul * * @return array A structured array of addresses. */ - function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) + public function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) { if (!isset($this) || !isset($this->mailRFC822)) { $obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit); @@ -222,11 +220,10 @@ function parseAddressList($address = null, $default_domain = null, $nest_groups /** * Splits an address into separate addresses. * - * @access private * @param string $address The addresses to split. * @return boolean Success or failure. */ - function _splitAddresses($address) + protected function _splitAddresses($address) { if (!empty($this->limit) && count($this->addresses) == $this->limit) { return ''; @@ -298,11 +295,10 @@ function _splitAddresses($address) /** * Checks for a group at the start of the string. * - * @access private * @param string $address The address to check. * @return boolean Whether or not there is a group at the start of the string. */ - function _isGroup($address) + protected function _isGroup($address) { // First comma not in quotes, angles or escaped: $parts = explode(',', $address); @@ -322,12 +318,11 @@ function _isGroup($address) /** * A common function that will check an exploded string. * - * @access private * @param array $parts The exloded string. * @param string $char The char that was exploded on. * @return mixed False if the string contains unclosed quotes/brackets, or the string on success. */ - function _splitCheck($parts, $char) + protected function _splitCheck($parts, $char) { $string = $parts[0]; @@ -355,12 +350,11 @@ function _splitCheck($parts, $char) /** * Checks if a string has unclosed quotes or not. * - * @access private * @param string $string The string to check. * @return boolean True if there are unclosed quotes inside the string, * false otherwise. */ - function _hasUnclosedQuotes($string) + protected function _hasUnclosedQuotes($string) { $string = trim($string); $iMax = strlen($string); @@ -392,12 +386,11 @@ function _hasUnclosedQuotes($string) * Checks if a string has an unclosed brackets or not. IMPORTANT: * This function handles both angle brackets and square brackets; * - * @access private * @param string $string The string to check. * @param string $chars The characters to check for. * @return boolean True if there are unclosed brackets inside the string, false otherwise. */ - function _hasUnclosedBrackets($string, $chars) + protected function _hasUnclosedBrackets($string, $chars) { $num_angle_start = substr_count($string, $chars[0]); $num_angle_end = substr_count($string, $chars[1]); @@ -416,13 +409,12 @@ function _hasUnclosedBrackets($string, $chars) /** * Sub function that is used only by hasUnclosedBrackets(). * - * @access private * @param string $string The string to check. * @param integer &$num The number of occurences. * @param string $char The character to count. * @return integer The number of occurences of $char in $string, adjusted for backslashes. */ - function _hasUnclosedBracketsSub($string, &$num, $char) + protected function _hasUnclosedBracketsSub($string, &$num, $char) { $parts = explode($char, $string); for ($i = 0; $i < count($parts); $i++){ @@ -438,11 +430,10 @@ function _hasUnclosedBracketsSub($string, &$num, $char) /** * Function to begin checking the address. * - * @access private * @param string $address The address to validate. * @return mixed False on failure, or a structured array of address information on success. */ - function _validateAddress($address) + protected function _validateAddress($address) { $is_group = false; $addresses = array(); @@ -483,14 +474,6 @@ function _validateAddress($address) $addresses[] = $address['address']; } - // Check that $addresses is set, if address like this: - // Groupname:; - // Then errors were appearing. - if (!count($addresses)){ - $this->error = 'Empty group.'; - return false; - } - // Trim the whitespace from all of the address strings. array_map('trim', $addresses); @@ -531,11 +514,10 @@ function _validateAddress($address) /** * Function to validate a phrase. * - * @access private * @param string $phrase The phrase to check. * @return boolean Success or failure. */ - function _validatePhrase($phrase) + protected function _validatePhrase($phrase) { // Splits on one or more Tab or space. $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY); @@ -572,11 +554,10 @@ function _validatePhrase($phrase) * can split a list of addresses up before encoding personal names * (umlauts, etc.), for example. * - * @access private * @param string $atom The string to check. * @return boolean Success or failure. */ - function _validateAtom($atom) + protected function _validateAtom($atom) { if (!$this->validate) { // Validation has been turned off; assume the atom is okay. @@ -605,11 +586,10 @@ function _validateAtom($atom) * Function to validate quoted string, which is: * quoted-string = <"> *(qtext/quoted-pair) <"> * - * @access private * @param string $qstring The string to check * @return boolean Success or failure. */ - function _validateQuotedString($qstring) + protected function _validateQuotedString($qstring) { // Leading and trailing " $qstring = substr($qstring, 1, -1); @@ -623,11 +603,10 @@ function _validateQuotedString($qstring) * mailbox = addr-spec ; simple address * / phrase route-addr ; name and route-addr * - * @access public * @param string &$mailbox The string to check. * @return boolean Success or failure. */ - function validateMailbox(&$mailbox) + public function validateMailbox(&$mailbox) { // A couple of defaults. $phrase = ''; @@ -712,11 +691,10 @@ function validateMailbox(&$mailbox) * Angle brackets have already been removed at the point of * getting to this function. * - * @access private * @param string $route_addr The string to check. * @return mixed False on failure, or an array containing validated address/route information on success. */ - function _validateRouteAddr($route_addr) + protected function _validateRouteAddr($route_addr) { // Check for colon. if (strpos($route_addr, ':') !== false) { @@ -762,11 +740,10 @@ function _validateRouteAddr($route_addr) * Function to validate a route, which is: * route = 1#("@" domain) ":" * - * @access private * @param string $route The string to check. * @return mixed False on failure, or the validated $route on success. */ - function _validateRoute($route) + protected function _validateRoute($route) { // Split on comma. $domains = explode(',', trim($route)); @@ -785,11 +762,10 @@ function _validateRoute($route) * * domain = sub-domain *("." sub-domain) * - * @access private * @param string $domain The string to check. * @return mixed False on failure, or the validated domain on success. */ - function _validateDomain($domain) + protected function _validateDomain($domain) { // Note the different use of $subdomains and $sub_domains $subdomains = explode('.', $domain); @@ -813,11 +789,10 @@ function _validateDomain($domain) * Function to validate a subdomain: * subdomain = domain-ref / domain-literal * - * @access private * @param string $subdomain The string to check. * @return boolean Success or failure. */ - function _validateSubdomain($subdomain) + protected function _validateSubdomain($subdomain) { if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){ if (!$this->_validateDliteral($arr[1])) return false; @@ -833,11 +808,10 @@ function _validateSubdomain($subdomain) * Function to validate a domain literal: * domain-literal = "[" *(dtext / quoted-pair) "]" * - * @access private * @param string $dliteral The string to check. * @return boolean Success or failure. */ - function _validateDliteral($dliteral) + protected function _validateDliteral($dliteral) { return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\'; } @@ -847,11 +821,10 @@ function _validateDliteral($dliteral) * * addr-spec = local-part "@" domain * - * @access private * @param string $addr_spec The string to check. * @return mixed False on failure, or the validated addr-spec on success. */ - function _validateAddrSpec($addr_spec) + protected function _validateAddrSpec($addr_spec) { $addr_spec = trim($addr_spec); @@ -878,17 +851,16 @@ function _validateAddrSpec($addr_spec) * Function to validate the local part of an address: * local-part = word *("." word) * - * @access private * @param string $local_part * @return mixed False on failure, or the validated local part on success. */ - function _validateLocalPart($local_part) + protected function _validateLocalPart($local_part) { $parts = explode('.', $local_part); $words = array(); // Split the local_part into words. - while (count($parts) > 0){ + while (count($parts) > 0) { $words[] = $this->_splitCheck($parts, '.'); for ($i = 0; $i < $this->index + 1; $i++) { array_shift($parts); @@ -897,6 +869,10 @@ function _validateLocalPart($local_part) // Validate each word. foreach ($words as $word) { + // word cannot be empty (#17317) + if ($word === '') { + return false; + } // If this word contains an unquoted space, it is invalid. (6.2.4) if (strpos($word, ' ') && $word[0] !== '"') { @@ -920,7 +896,7 @@ function _validateLocalPart($local_part) * @param string $data Addresses to count * @return int Approximate count */ - function approximateCount($data) + public function approximateCount($data) { return count(preg_split('/(? * @copyright 2010 Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: mail.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @version CVS: $Id$ * @link http://pear.php.net/package/Mail/ */ /** * internal PHP-mail() implementation of the PEAR Mail:: interface. * @package Mail - * @version $Revision: 294747 $ + * @version $Revision$ */ class Mail_mail extends Mail { @@ -64,7 +64,7 @@ class Mail_mail extends Mail { * * @param array $params Extra arguments for the mail() function. */ - function Mail_mail($params = null) + public function __construct($params = null) { // The other mail implementations accept parameters as arrays. // In the interest of being consistent, explode an array into @@ -109,10 +109,8 @@ function Mail_mail($params = null) * @return mixed Returns true on success, or a PEAR_Error * containing a descriptive error message on * failure. - * - * @access public */ - function send($recipients, $headers, $body) + public function send($recipients, $headers, $body) { if (!is_array($headers)) { return PEAR::raiseError('$headers must be an array'); diff --git a/WEB-INF/lib/pear/Mail/mock.php b/WEB-INF/lib/pear/Mail/mock.php index 61570ba40..e3e290bdc 100644 --- a/WEB-INF/lib/pear/Mail/mock.php +++ b/WEB-INF/lib/pear/Mail/mock.php @@ -2,7 +2,7 @@ /** * Mock implementation * - * PHP versions 4 and 5 + * PHP version 5 * * LICENSE: * @@ -39,7 +39,7 @@ * @author Chuck Hagenbuch * @copyright 2010 Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: mock.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @version CVS: $Id$ * @link http://pear.php.net/package/Mail/ */ @@ -47,7 +47,7 @@ * Mock implementation of the PEAR Mail:: interface for testing. * @access public * @package Mail - * @version $Revision: 294747 $ + * @version $Revision$ */ class Mail_mock extends Mail { @@ -55,23 +55,22 @@ class Mail_mock extends Mail { * Array of messages that have been sent with the mock. * * @var array - * @access public */ - var $sentMessages = array(); + public $sentMessages = array(); /** * Callback before sending mail. * * @var callback */ - var $_preSendCallback; + protected $_preSendCallback; /** * Callback after sending mai. * * @var callback */ - var $_postSendCallback; + protected $_postSendCallback; /** * Constructor. @@ -82,9 +81,8 @@ class Mail_mock extends Mail { * postSendCallback Called after an email would have been sent. * * @param array Hash containing any parameters. - * @access public */ - function Mail_mock($params) + public function __construct($params) { if (isset($params['preSendCallback']) && is_callable($params['preSendCallback'])) { @@ -120,9 +118,8 @@ function Mail_mock($params) * @return mixed Returns true on success, or a PEAR_Error * containing a descriptive error message on * failure. - * @access public */ - function send($recipients, $headers, $body) + public function send($recipients, $headers, $body) { if ($this->_preSendCallback) { call_user_func_array($this->_preSendCallback, diff --git a/WEB-INF/lib/pear/Mail/null.php b/WEB-INF/lib/pear/Mail/null.php index f8d58272e..7896a4299 100644 --- a/WEB-INF/lib/pear/Mail/null.php +++ b/WEB-INF/lib/pear/Mail/null.php @@ -2,7 +2,7 @@ /** * Null implementation of the PEAR Mail interface * - * PHP versions 4 and 5 + * PHP version 5 * * LICENSE: * @@ -39,7 +39,7 @@ * @author Phil Kernick * @copyright 2010 Phil Kernick * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: null.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @version CVS: $Id$ * @link http://pear.php.net/package/Mail/ */ @@ -47,7 +47,7 @@ * Null implementation of the PEAR Mail:: interface. * @access public * @package Mail - * @version $Revision: 294747 $ + * @version $Revision$ */ class Mail_null extends Mail { @@ -74,9 +74,8 @@ class Mail_null extends Mail { * @return mixed Returns true on success, or a PEAR_Error * containing a descriptive error message on * failure. - * @access public */ - function send($recipients, $headers, $body) + public function send($recipients, $headers, $body) { return true; } diff --git a/WEB-INF/lib/pear/Mail/sendmail.php b/WEB-INF/lib/pear/Mail/sendmail.php index b056575e9..f8866bdf3 100644 --- a/WEB-INF/lib/pear/Mail/sendmail.php +++ b/WEB-INF/lib/pear/Mail/sendmail.php @@ -1,7 +1,7 @@ sendmail_path = $params['sendmail_path']; @@ -100,9 +99,8 @@ function Mail_sendmail($params) * @return mixed Returns true on success, or a PEAR_Error * containing a descriptive error message on * failure. - * @access public */ - function send($recipients, $headers, $body) + public function send($recipients, $headers, $body) { if (!is_array($headers)) { return PEAR::raiseError('$headers must be an array'); diff --git a/WEB-INF/lib/pear/Mail/smtp.php b/WEB-INF/lib/pear/Mail/smtp.php index 52ea60208..d446b1bcd 100644 --- a/WEB-INF/lib/pear/Mail/smtp.php +++ b/WEB-INF/lib/pear/Mail/smtp.php @@ -2,7 +2,7 @@ /** * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class. * - * PHP versions 4 and 5 + * PHP version 5 * * LICENSE: * @@ -40,7 +40,7 @@ * @author Chuck Hagenbuch * @copyright 2010 Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: smtp.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @version CVS: $Id$ * @link http://pear.php.net/package/Mail/ */ @@ -69,7 +69,7 @@ * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class. * @access public * @package Mail - * @version $Revision: 294747 $ + * @version $Revision$ */ class Mail_smtp extends Mail { @@ -162,6 +162,8 @@ class Mail_smtp extends Mail { * @var bool */ var $pipelining; + + var $socket_options = array(); /** * Constructor. @@ -186,9 +188,8 @@ class Mail_smtp extends Mail { * * @param array Hash containing any parameters different from the * defaults. - * @access public */ - function Mail_smtp($params) + public function __construct($params) { if (isset($params['host'])) $this->host = $params['host']; if (isset($params['port'])) $this->port = $params['port']; @@ -200,20 +201,18 @@ function Mail_smtp($params) if (isset($params['debug'])) $this->debug = (bool)$params['debug']; if (isset($params['persist'])) $this->persist = (bool)$params['persist']; if (isset($params['pipelining'])) $this->pipelining = (bool)$params['pipelining']; - + if (isset($params['socket_options'])) $this->socket_options = $params['socket_options']; // Deprecated options if (isset($params['verp'])) { $this->addServiceExtensionParameter('XVERP', is_bool($params['verp']) ? null : $params['verp']); } - - register_shutdown_function(array(&$this, '_Mail_smtp')); } /** * Destructor implementation to ensure that we disconnect from any * potentially-alive persistent SMTP connections. */ - function _Mail_smtp() + public function __destruct() { $this->disconnect(); } @@ -240,12 +239,11 @@ function _Mail_smtp() * @return mixed Returns true on success, or a PEAR_Error * containing a descriptive error message on * failure. - * @access public */ - function send($recipients, $headers, $body) + public function send($recipients, $headers, $body) { /* If we don't already have an SMTP object, create one. */ - $result = &$this->getSMTPObject(); + $result = $this->getSMTPObject(); if (PEAR::isError($result)) { return $result; } @@ -304,7 +302,7 @@ function send($recipients, $headers, $body) } /* Send the message's headers and the body as SMTP data. */ - $res = $this->_smtp->data($textHeaders . "\r\n\r\n" . $body); + $res = $this->_smtp->data($body, $textHeaders); list(,$args) = $this->_smtp->getResponse(); if (preg_match("/Ok: queued as (.*)/", $args, $queued)) { @@ -337,18 +335,20 @@ function send($recipients, $headers, $body) * failure. * * @since 1.2.0 - * @access public */ - function &getSMTPObject() + public function getSMTPObject() { if (is_object($this->_smtp) !== false) { return $this->_smtp; } include_once 'Net/SMTP.php'; - $this->_smtp = &new Net_SMTP($this->host, + $this->_smtp = new Net_SMTP($this->host, $this->port, - $this->localhost); + $this->localhost, + $this->pipelining, + 0, + $this->socket_options); /* If we still don't have an SMTP object at this point, fail. */ if (is_object($this->_smtp) === false) { @@ -393,9 +393,8 @@ function &getSMTPObject() * @param string Any value the keyword needs. * * @since 1.2.0 - * @access public */ - function addServiceExtensionParameter($keyword, $value = null) + public function addServiceExtensionParameter($keyword, $value = null) { $this->_extparams[$keyword] = $value; } @@ -406,9 +405,8 @@ function addServiceExtensionParameter($keyword, $value = null) * @return boolean True if the SMTP connection no longer exists. * * @since 1.1.9 - * @access public */ - function disconnect() + public function disconnect() { /* If we have an SMTP object, disconnect and destroy it. */ if (is_object($this->_smtp) && $this->_smtp->disconnect()) { @@ -428,9 +426,8 @@ function disconnect() * @return string A string describing the current SMTP error. * * @since 1.1.7 - * @access private */ - function _error($text, &$error) + protected function _error($text, $error) { /* Split the SMTP response into a code and a response string. */ list($code, $response) = $this->_smtp->getResponse(); diff --git a/WEB-INF/lib/pear/Mail/smtpmx.php b/WEB-INF/lib/pear/Mail/smtpmx.php index f0b694086..6eb8bec2e 100644 --- a/WEB-INF/lib/pear/Mail/smtpmx.php +++ b/WEB-INF/lib/pear/Mail/smtpmx.php @@ -1,4 +1,4 @@ - * @copyright 2010 gERD Schaufelberger * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: smtpmx.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @version CVS: $Id$ * @link http://pear.php.net/package/Mail/ */ @@ -56,7 +56,7 @@ * @access public * @author gERD Schaufelberger * @package Mail - * @version $Revision: 294747 $ + * @version $Revision$ */ class Mail_smtpmx extends Mail { @@ -386,7 +386,7 @@ function send($recipients, $headers, $body) } // Send data - $res = $this->_smtp->data("$textHeaders\r\n$body"); + $res = $this->_smtp->data($body, $textHeaders); if (is_a($res, 'PEAR_Error')) { $info = array('rcpt' => $rcpt); return $this->_raiseError('failed_send_data', $info); diff --git a/WEB-INF/lib/pear/Net/SMTP.php b/WEB-INF/lib/pear/Net/SMTP.php index ea4b55e8d..dd822a565 100644 --- a/WEB-INF/lib/pear/Net/SMTP.php +++ b/WEB-INF/lib/pear/Net/SMTP.php @@ -1,14 +1,14 @@ | // | Damian Alejandro Fernandez Sosa | // +----------------------------------------------------------------------+ -// -// $Id: SMTP.php 293948 2010-01-24 21:46:00Z jon $ require_once 'PEAR.php'; require_once 'Net/Socket.php'; /** * Provides an implementation of the SMTP protocol using PEAR's - * Net_Socket:: class. + * Net_Socket class. * * @package Net_SMTP * @author Chuck Hagenbuch * @author Jon Parise * @author Damian Alejandro Fernandez Sosa * - * @example basic.php A basic implementation of the Net_SMTP package. + * @example basic.php A basic implementation of the Net_SMTP package. */ class Net_SMTP { /** * The server to connect to. * @var string - * @access public */ - var $host = 'localhost'; + public $host = 'localhost'; /** * The port to connect to. * @var int - * @access public */ - var $port = 25; + public $port = 25; /** * The value to give when sending EHLO or HELO. * @var string - * @access public */ - var $localhost = 'localhost'; + public $localhost = 'localhost'; /** * List of supported authentication methods, in preferential order. * @var array - * @access public */ - var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN'); + public $auth_methods = array(); /** * Use SMTP command pipelining (specified in RFC 2920) if the SMTP @@ -73,65 +67,69 @@ class Net_SMTP * SMTP server but return immediately. * * @var bool - * @access public */ - var $pipelining = false; + public $pipelining = false; /** * Number of pipelined commands. * @var int - * @access private */ - var $_pipelined_commands = 0; + protected $pipelined_commands = 0; /** * Should debugging output be enabled? * @var boolean - * @access private */ - var $_debug = false; + protected $debug = false; /** * Debug output handler. * @var callback - * @access private */ - var $_debug_handler = null; + protected $debug_handler = null; /** * The socket resource being used to connect to the SMTP server. * @var resource - * @access private */ - var $_socket = null; + protected $socket = null; + + /** + * Array of socket options that will be passed to Net_Socket::connect(). + * @see stream_context_create() + * @var array + */ + protected $socket_options = null; + + /** + * The socket I/O timeout value in seconds. + * @var int + */ + protected $timeout = 0; /** * The most recent server response code. * @var int - * @access private */ - var $_code = -1; + protected $code = -1; /** * The most recent server response arguments. * @var array - * @access private */ - var $_arguments = array(); + protected $arguments = array(); /** * Stores the SMTP server's greeting string. * @var string - * @access private */ - var $_greeting = null; + protected $greeting = null; /** * Stores detected features of the SMTP server. * @var array - * @access private */ - var $_esmtp = array(); + protected $esmtp = array(); /** * Instantiates a new Net_SMTP object, overriding any defaults @@ -144,16 +142,18 @@ class Net_SMTP * $smtp = new Net_SMTP('ssl://mail.host.com', 465); * $smtp->connect(); * - * @param string $host The server to connect to. - * @param integer $port The port to connect to. - * @param string $localhost The value to give when sending EHLO or HELO. - * @param boolean $pipeling Use SMTP command pipelining + * @param string $host The server to connect to. + * @param integer $port The port to connect to. + * @param string $localhost The value to give when sending EHLO or HELO. + * @param boolean $pipelining Use SMTP command pipelining + * @param integer $timeout Socket I/O timeout in seconds. + * @param array $socket_options Socket stream_context_create() options. * - * @access public - * @since 1.0 + * @since 1.0 */ - function Net_SMTP($host = null, $port = null, $localhost = null, $pipelining = false) - { + public function __construct($host = null, $port = null, $localhost = null, + $pipelining = false, $timeout = 0, $socket_options = null + ) { if (isset($host)) { $this->host = $host; } @@ -163,49 +163,65 @@ function Net_SMTP($host = null, $port = null, $localhost = null, $pipelining = f if (isset($localhost)) { $this->localhost = $localhost; } - $this->pipelining = $pipelining; - $this->_socket = new Net_Socket(); + $this->pipelining = $pipelining; + $this->socket = new Net_Socket(); + $this->socket_options = $socket_options; + $this->timeout = $timeout; - /* Include the Auth_SASL package. If the package is not - * available, we disable the authentication methods that - * depend upon it. */ - if ((@include_once 'Auth/SASL.php') === false) { - $pos = array_search('DIGEST-MD5', $this->auth_methods); - unset($this->auth_methods[$pos]); - $pos = array_search('CRAM-MD5', $this->auth_methods); - unset($this->auth_methods[$pos]); + /* Include the Auth_SASL package. If the package is available, we + * enable the authentication methods that depend upon it. */ + if (@include_once 'Auth/SASL.php') { + $this->setAuthMethod('CRAM-MD5', array($this, 'authCramMD5')); + $this->setAuthMethod('DIGEST-MD5', array($this, 'authDigestMD5')); } + + /* These standard authentication methods are always available. */ + $this->setAuthMethod('LOGIN', array($this, 'authLogin'), false); + $this->setAuthMethod('PLAIN', array($this, 'authPlain'), false); + } + + /** + * Set the socket I/O timeout value in seconds plus microseconds. + * + * @param integer $seconds Timeout value in seconds. + * @param integer $microseconds Additional value in microseconds. + * + * @since 1.5.0 + */ + public function setTimeout($seconds, $microseconds = 0) + { + return $this->socket->setTimeout($seconds, $microseconds); } /** * Set the value of the debugging flag. * - * @param boolean $debug New value for the debugging flag. + * @param boolean $debug New value for the debugging flag. + * @param callback $handler Debug handler callback * - * @access public - * @since 1.1.0 + * @since 1.1.0 */ - function setDebug($debug, $handler = null) + public function setDebug($debug, $handler = null) { - $this->_debug = $debug; - $this->_debug_handler = $handler; + $this->debug = $debug; + $this->debug_handler = $handler; } /** * Write the given debug text to the current debug output handler. * - * @param string $message Debug mesage text. + * @param string $message Debug mesage text. * - * @access private - * @since 1.3.3 + * @since 1.3.3 */ - function _debug($message) + protected function debug($message) { - if ($this->_debug) { - if ($this->_debug_handler) { - call_user_func_array($this->_debug_handler, - array(&$this, $message)); + if ($this->debug) { + if ($this->debug_handler) { + call_user_func_array( + $this->debug_handler, array(&$this, $message) + ); } else { echo "DEBUG: $message\n"; } @@ -215,24 +231,24 @@ function _debug($message) /** * Send the given string of data to the server. * - * @param string $data The string of data to send. + * @param string $data The string of data to send. * - * @return mixed True on success or a PEAR_Error object on failure. + * @return mixed The number of bytes that were actually written, + * or a PEAR_Error object on failure. * - * @access private - * @since 1.1.0 + * @since 1.1.0 */ - function _send($data) + protected function send($data) { - $this->_debug("Send: $data"); + $this->debug("Send: $data"); - $error = $this->_socket->write($data); - if ($error === false || PEAR::isError($error)) { - $msg = ($error) ? $error->getMessage() : "unknown error"; + $result = $this->socket->write($data); + if (!$result || PEAR::isError($result)) { + $msg = $result ? $result->getMessage() : "unknown error"; return PEAR::raiseError("Failed to write to socket: $msg"); } - return true; + return $result; } /** @@ -240,19 +256,18 @@ function _send($data) * arguments. A carriage return / linefeed (CRLF) sequence will * be appended to each command string before it is sent to the * SMTP server - an error will be thrown if the command string - * already contains any newline characters. Use _send() for + * already contains any newline characters. Use send() for * commands that must contain newlines. * - * @param string $command The SMTP command to send to the server. - * @param string $args A string of optional arguments to append - * to the command. + * @param string $command The SMTP command to send to the server. + * @param string $args A string of optional arguments to append + * to the command. * - * @return mixed The result of the _send() call. + * @return mixed The result of the send() call. * - * @access private - * @since 1.1.0 + * @since 1.1.0 */ - function _put($command, $args = '') + protected function put($command, $args = '') { if (!empty($args)) { $command .= ' ' . $args; @@ -262,57 +277,56 @@ function _put($command, $args = '') return PEAR::raiseError('Commands cannot contain newlines'); } - return $this->_send($command . "\r\n"); + return $this->send($command . "\r\n"); } /** * Read a reply from the SMTP server. The reply consists of a response * code and a response message. * - * @param mixed $valid The set of valid response codes. These - * may be specified as an array of integer - * values or as a single integer value. - * @param bool $later Do not parse the response now, but wait - * until the last command in the pipelined - * command group + * @param mixed $valid The set of valid response codes. These + * may be specified as an array of integer + * values or as a single integer value. + * @param bool $later Do not parse the response now, but wait + * until the last command in the pipelined + * command group * - * @return mixed True if the server returned a valid response code or - * a PEAR_Error object is an error condition is reached. + * @return mixed True if the server returned a valid response code or + * a PEAR_Error object is an error condition is reached. * - * @access private - * @since 1.1.0 + * @since 1.1.0 * - * @see getResponse + * @see getResponse */ - function _parseResponse($valid, $later = false) + protected function parseResponse($valid, $later = false) { - $this->_code = -1; - $this->_arguments = array(); + $this->code = -1; + $this->arguments = array(); if ($later) { - $this->_pipelined_commands++; + $this->pipelined_commands++; return true; } - for ($i = 0; $i <= $this->_pipelined_commands; $i++) { - while ($line = $this->_socket->readLine()) { - $this->_debug("Recv: $line"); + for ($i = 0; $i <= $this->pipelined_commands; $i++) { + while ($line = $this->socket->readLine()) { + $this->debug("Recv: $line"); - /* If we receive an empty line, the connection has been closed. */ + /* If we receive an empty line, the connection was closed. */ if (empty($line)) { $this->disconnect(); - return PEAR::raiseError('Connection was unexpectedly closed'); + return PEAR::raiseError('Connection was closed'); } /* Read the code and store the rest in the arguments array. */ $code = substr($line, 0, 3); - $this->_arguments[] = trim(substr($line, 4)); + $this->arguments[] = trim(substr($line, 4)); /* Check the syntax of the response code. */ if (is_numeric($code)) { - $this->_code = (int)$code; + $this->code = (int)$code; } else { - $this->_code = -1; + $this->code = -1; break; } @@ -323,79 +337,115 @@ function _parseResponse($valid, $later = false) } } - $this->_pipelined_commands = 0; + $this->pipelined_commands = 0; /* Compare the server's response code with the valid code/codes. */ - if (is_int($valid) && ($this->_code === $valid)) { + if (is_int($valid) && ($this->code === $valid)) { return true; - } elseif (is_array($valid) && in_array($this->_code, $valid, true)) { + } elseif (is_array($valid) && in_array($this->code, $valid, true)) { return true; } - return PEAR::raiseError('Invalid response code received from server', - $this->_code); + return PEAR::raiseError('Invalid response code received from server', $this->code); + } + + /** + * Issue an SMTP command and verify its response. + * + * @param string $command The SMTP command string or data. + * @param mixed $valid The set of valid response codes. These + * may be specified as an array of integer + * values or as a single integer value. + * + * @return mixed True on success or a PEAR_Error object on failure. + * + * @since 1.6.0 + */ + public function command($command, $valid) + { + if (PEAR::isError($error = $this->put($command))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse($valid))) { + return $error; + } + + return true; } /** * Return a 2-tuple containing the last response from the SMTP server. * - * @return array A two-element array: the first element contains the - * response code as an integer and the second element - * contains the response's arguments as a string. + * @return array A two-element array: the first element contains the + * response code as an integer and the second element + * contains the response's arguments as a string. * - * @access public - * @since 1.1.0 + * @since 1.1.0 */ - function getResponse() + public function getResponse() { - return array($this->_code, join("\n", $this->_arguments)); + return array($this->code, join("\n", $this->arguments)); } /** * Return the SMTP server's greeting string. * - * @return string A string containing the greeting string, or null if a - * greeting has not been received. + * @return string A string containing the greeting string, or null if + * a greeting has not been received. * - * @access public - * @since 1.3.3 + * @since 1.3.3 */ - function getGreeting() + public function getGreeting() { - return $this->_greeting; + return $this->greeting; } /** * Attempt to connect to the SMTP server. * - * @param int $timeout The timeout value (in seconds) for the - * socket connection. - * @param bool $persistent Should a persistent socket connection - * be used? + * @param int $timeout The timeout value (in seconds) for the + * socket connection attempt. + * @param bool $persistent Should a persistent socket connection + * be used? * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access public - * @since 1.0 + * @since 1.0 */ - function connect($timeout = null, $persistent = false) + public function connect($timeout = null, $persistent = false) { - $this->_greeting = null; - $result = $this->_socket->connect($this->host, $this->port, - $persistent, $timeout); + $this->greeting = null; + + $result = $this->socket->connect( + $this->host, $this->port, $persistent, $timeout, $this->socket_options + ); + if (PEAR::isError($result)) { - return PEAR::raiseError('Failed to connect socket: ' . - $result->getMessage()); + return PEAR::raiseError( + 'Failed to connect socket: ' . $result->getMessage() + ); + } + + /* + * Now that we're connected, reset the socket's timeout value for + * future I/O operations. This allows us to have different socket + * timeout values for the initial connection (our $timeout parameter) + * and all other socket operations. + */ + if ($this->timeout > 0) { + if (PEAR::isError($error = $this->setTimeout($this->timeout))) { + return $error; + } } - if (PEAR::isError($error = $this->_parseResponse(220))) { + if (PEAR::isError($error = $this->parseResponse(220))) { return $error; } /* Extract and store a copy of the server's greeting string. */ - list(, $this->_greeting) = $this->getResponse(); + list(, $this->greeting) = $this->getResponse(); - if (PEAR::isError($error = $this->_negotiate())) { + if (PEAR::isError($error = $this->negotiate())) { return $error; } @@ -407,20 +457,20 @@ function connect($timeout = null, $persistent = false) * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access public - * @since 1.0 + * @since 1.0 */ - function disconnect() + public function disconnect() { - if (PEAR::isError($error = $this->_put('QUIT'))) { + if (PEAR::isError($error = $this->put('QUIT'))) { return $error; } - if (PEAR::isError($error = $this->_parseResponse(221))) { + if (PEAR::isError($error = $this->parseResponse(221))) { return $error; } - if (PEAR::isError($error = $this->_socket->disconnect())) { - return PEAR::raiseError('Failed to disconnect socket: ' . - $error->getMessage()); + if (PEAR::isError($error = $this->socket->disconnect())) { + return PEAR::raiseError( + 'Failed to disconnect socket: ' . $error->getMessage() + ); } return true; @@ -433,40 +483,34 @@ function disconnect() * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. * - * @access private - * @since 1.1.0 + * @since 1.1.0 */ - function _negotiate() + protected function negotiate() { - if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) { + if (PEAR::isError($error = $this->put('EHLO', $this->localhost))) { return $error; } - if (PEAR::isError($this->_parseResponse(250))) { - /* If we receive a 503 response, we're already authenticated. */ - if ($this->_code === 503) { - return true; - } - + if (PEAR::isError($this->parseResponse(250))) { /* If the EHLO failed, try the simpler HELO command. */ - if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) { + if (PEAR::isError($error = $this->put('HELO', $this->localhost))) { return $error; } - if (PEAR::isError($this->_parseResponse(250))) { - return PEAR::raiseError('HELO was not accepted: ', $this->_code); + if (PEAR::isError($this->parseResponse(250))) { + return PEAR::raiseError('HELO was not accepted', $this->code); } return true; } - foreach ($this->_arguments as $argument) { - $verb = strtok($argument, ' '); - $arguments = substr($argument, strlen($verb) + 1, - strlen($argument) - strlen($verb) - 1); - $this->_esmtp[$verb] = $arguments; + foreach ($this->arguments as $argument) { + $verb = strtok($argument, ' '); + $len = strlen($verb); + $arguments = substr($argument, $len + 1, strlen($argument) - $len - 1); + $this->esmtp[$verb] = $arguments; } - if (!isset($this->_esmtp['PIPELINING'])) { + if (!isset($this->esmtp['PIPELINING'])) { $this->pipelining = false; } @@ -477,17 +521,16 @@ function _negotiate() * Returns the name of the best authentication method that the server * has advertised. * - * @return mixed Returns a string containing the name of the best - * supported authentication method or a PEAR_Error object - * if a failure condition is encountered. - * @access private - * @since 1.1.0 + * @return mixed Returns a string containing the name of the best + * supported authentication method or a PEAR_Error object + * if a failure condition is encountered. + * @since 1.1.0 */ - function _getBestAuthMethod() + protected function getBestAuthMethod() { - $available_methods = explode(' ', $this->_esmtp['AUTH']); + $available_methods = explode(' ', $this->esmtp['AUTH']); - foreach ($this->auth_methods as $method) { + foreach ($this->auth_methods as $method => $callback) { if (in_array($method, $available_methods)) { return $method; } @@ -499,35 +542,47 @@ function _getBestAuthMethod() /** * Attempt to do SMTP authentication. * - * @param string The userid to authenticate as. - * @param string The password to authenticate with. - * @param string The requested authentication method. If none is - * specified, the best supported method will be used. - * @param bool Flag indicating whether or not TLS should be attempted. + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $method The requested authentication method. If none is + * specified, the best supported method will be used. + * @param bool $tls Flag indicating whether or not TLS should be attempted. + * @param string $authz An optional authorization identifier. If specified, this + * identifier will be used as the authorization proxy. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access public - * @since 1.0 + * @since 1.0 */ - function auth($uid, $pwd , $method = '', $tls = true) + public function auth($uid, $pwd , $method = '', $tls = true, $authz = '') { /* We can only attempt a TLS connection if one has been requested, - * we're running PHP 5.1.0 or later, have access to the OpenSSL - * extension, are connected to an SMTP server which supports the - * STARTTLS extension, and aren't already connected over a secure + * we're running PHP 5.1.0 or later, have access to the OpenSSL + * extension, are connected to an SMTP server which supports the + * STARTTLS extension, and aren't already connected over a secure * (SSL) socket connection. */ - if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') && - extension_loaded('openssl') && isset($this->_esmtp['STARTTLS']) && - strncasecmp($this->host, 'ssl://', 6) !== 0) { + if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') + && extension_loaded('openssl') && isset($this->esmtp['STARTTLS']) + && strncasecmp($this->host, 'ssl://', 6) !== 0 + ) { /* Start the TLS connection attempt. */ - if (PEAR::isError($result = $this->_put('STARTTLS'))) { + if (PEAR::isError($result = $this->put('STARTTLS'))) { return $result; } - if (PEAR::isError($result = $this->_parseResponse(220))) { + if (PEAR::isError($result = $this->parseResponse(220))) { return $result; } - if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) { + if (isset($this->socket_options['ssl']['crypto_method'])) { + $crypto_method = $this->socket_options['ssl']['crypto_method']; + } else { + /* STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT constant does not exist + * and STREAM_CRYPTO_METHOD_SSLv23_CLIENT constant is + * inconsistent across PHP versions. */ + $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT + | @STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT + | @STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + } + if (PEAR::isError($result = $this->socket->enableCrypto(true, $crypto_method))) { return $result; } elseif ($result !== true) { return PEAR::raiseError('STARTTLS failed'); @@ -535,47 +590,41 @@ function auth($uid, $pwd , $method = '', $tls = true) /* Send EHLO again to recieve the AUTH string from the * SMTP server. */ - $this->_negotiate(); + $this->negotiate(); } - if (empty($this->_esmtp['AUTH'])) { + if (empty($this->esmtp['AUTH'])) { return PEAR::raiseError('SMTP server does not support authentication'); } /* If no method has been specified, get the name of the best * supported method advertised by the SMTP server. */ if (empty($method)) { - if (PEAR::isError($method = $this->_getBestAuthMethod())) { + if (PEAR::isError($method = $this->getBestAuthMethod())) { /* Return the PEAR_Error object from _getBestAuthMethod(). */ return $method; } } else { $method = strtoupper($method); - if (!in_array($method, $this->auth_methods)) { + if (!array_key_exists($method, $this->auth_methods)) { return PEAR::raiseError("$method is not a supported authentication method"); } } - switch ($method) { - case 'DIGEST-MD5': - $result = $this->_authDigest_MD5($uid, $pwd); - break; - - case 'CRAM-MD5': - $result = $this->_authCRAM_MD5($uid, $pwd); - break; - - case 'LOGIN': - $result = $this->_authLogin($uid, $pwd); - break; + if (!isset($this->auth_methods[$method])) { + return PEAR::raiseError("$method is not a supported authentication method"); + } - case 'PLAIN': - $result = $this->_authPlain($uid, $pwd); - break; + if (!is_callable($this->auth_methods[$method], false)) { + return PEAR::raiseError("$method authentication method cannot be called"); + } - default: - $result = PEAR::raiseError("$method is not a supported authentication method"); - break; + if (is_array($this->auth_methods[$method])) { + list($object, $method) = $this->auth_methods[$method]; + $result = $object->{$method}($uid, $pwd, $authz, $this); + } else { + $func = $this->auth_methods[$method]; + $result = $func($uid, $pwd, $authz, $this); } /* If an error was encountered, return the PEAR_Error object. */ @@ -586,52 +635,94 @@ function auth($uid, $pwd , $method = '', $tls = true) return true; } + /** + * Add a new authentication method. + * + * @param string $name The authentication method name (e.g. 'PLAIN') + * @param mixed $callback The authentication callback (given as the name of a + * function or as an (object, method name) array). + * @param bool $prepend Should the new method be prepended to the list of + * available methods? This is the default behavior, + * giving the new method the highest priority. + * + * @return mixed True on success or a PEAR_Error object on failure. + * + * @since 1.6.0 + */ + public function setAuthMethod($name, $callback, $prepend = true) + { + if (!is_string($name)) { + return PEAR::raiseError('Method name is not a string'); + } + + if (!is_string($callback) && !is_array($callback)) { + return PEAR::raiseError('Method callback must be string or array'); + } + + if (is_array($callback)) { + if (!is_object($callback[0]) || !is_string($callback[1])) { + return PEAR::raiseError('Bad mMethod callback array'); + } + } + + if ($prepend) { + $this->auth_methods = array_merge( + array($name => $callback), $this->auth_methods + ); + } else { + $this->auth_methods[$name] = $callback; + } + + return true; + } + /** * Authenticates the user using the DIGEST-MD5 method. * - * @param string The userid to authenticate as. - * @param string The password to authenticate with. + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access private - * @since 1.1.0 + * @since 1.1.0 */ - function _authDigest_MD5($uid, $pwd) + protected function authDigestMD5($uid, $pwd, $authz = '') { - if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) { + if (PEAR::isError($error = $this->put('AUTH', 'DIGEST-MD5'))) { return $error; } /* 334: Continue authentication request */ - if (PEAR::isError($error = $this->_parseResponse(334))) { + if (PEAR::isError($error = $this->parseResponse(334))) { /* 503: Error: already authenticated */ - if ($this->_code === 503) { + if ($this->code === 503) { return true; } return $error; } - $challenge = base64_decode($this->_arguments[0]); - $digest = &Auth_SASL::factory('digestmd5'); - $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge, - $this->host, "smtp")); + $digest = Auth_SASL::factory('digest-md5'); + $challenge = base64_decode($this->arguments[0]); + $auth_str = base64_encode( + $digest->getResponse($uid, $pwd, $challenge, $this->host, "smtp", $authz) + ); - if (PEAR::isError($error = $this->_put($auth_str))) { + if (PEAR::isError($error = $this->put($auth_str))) { return $error; } /* 334: Continue authentication request */ - if (PEAR::isError($error = $this->_parseResponse(334))) { + if (PEAR::isError($error = $this->parseResponse(334))) { return $error; } /* We don't use the protocol's third step because SMTP doesn't * allow subsequent authentication, so we just silently ignore * it. */ - if (PEAR::isError($error = $this->_put(''))) { + if (PEAR::isError($error = $this->put(''))) { return $error; } /* 235: Authentication successful */ - if (PEAR::isError($error = $this->_parseResponse(235))) { + if (PEAR::isError($error = $this->parseResponse(235))) { return $error; } } @@ -639,38 +730,38 @@ function _authDigest_MD5($uid, $pwd) /** * Authenticates the user using the CRAM-MD5 method. * - * @param string The userid to authenticate as. - * @param string The password to authenticate with. + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access private - * @since 1.1.0 + * @since 1.1.0 */ - function _authCRAM_MD5($uid, $pwd) + protected function authCRAMMD5($uid, $pwd, $authz = '') { - if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) { + if (PEAR::isError($error = $this->put('AUTH', 'CRAM-MD5'))) { return $error; } /* 334: Continue authentication request */ - if (PEAR::isError($error = $this->_parseResponse(334))) { + if (PEAR::isError($error = $this->parseResponse(334))) { /* 503: Error: already authenticated */ - if ($this->_code === 503) { + if ($this->code === 503) { return true; } return $error; } - $challenge = base64_decode($this->_arguments[0]); - $cram = &Auth_SASL::factory('crammd5'); - $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge)); + $challenge = base64_decode($this->arguments[0]); + $cram = Auth_SASL::factory('cram-md5'); + $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge)); - if (PEAR::isError($error = $this->_put($auth_str))) { + if (PEAR::isError($error = $this->put($auth_str))) { return $error; } /* 235: Authentication successful */ - if (PEAR::isError($error = $this->_parseResponse(235))) { + if (PEAR::isError($error = $this->parseResponse(235))) { return $error; } } @@ -678,42 +769,42 @@ function _authCRAM_MD5($uid, $pwd) /** * Authenticates the user using the LOGIN method. * - * @param string The userid to authenticate as. - * @param string The password to authenticate with. + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access private - * @since 1.1.0 + * @since 1.1.0 */ - function _authLogin($uid, $pwd) + protected function authLogin($uid, $pwd, $authz = '') { - if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) { + if (PEAR::isError($error = $this->put('AUTH', 'LOGIN'))) { return $error; } /* 334: Continue authentication request */ - if (PEAR::isError($error = $this->_parseResponse(334))) { + if (PEAR::isError($error = $this->parseResponse(334))) { /* 503: Error: already authenticated */ - if ($this->_code === 503) { + if ($this->code === 503) { return true; } return $error; } - if (PEAR::isError($error = $this->_put(base64_encode($uid)))) { + if (PEAR::isError($error = $this->put(base64_encode($uid)))) { return $error; } /* 334: Continue authentication request */ - if (PEAR::isError($error = $this->_parseResponse(334))) { + if (PEAR::isError($error = $this->parseResponse(334))) { return $error; } - if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) { + if (PEAR::isError($error = $this->put(base64_encode($pwd)))) { return $error; } /* 235: Authentication successful */ - if (PEAR::isError($error = $this->_parseResponse(235))) { + if (PEAR::isError($error = $this->parseResponse(235))) { return $error; } @@ -723,36 +814,36 @@ function _authLogin($uid, $pwd) /** * Authenticates the user using the PLAIN method. * - * @param string The userid to authenticate as. - * @param string The password to authenticate with. + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access private - * @since 1.1.0 + * @since 1.1.0 */ - function _authPlain($uid, $pwd) + protected function authPlain($uid, $pwd, $authz = '') { - if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) { + if (PEAR::isError($error = $this->put('AUTH', 'PLAIN'))) { return $error; } /* 334: Continue authentication request */ - if (PEAR::isError($error = $this->_parseResponse(334))) { + if (PEAR::isError($error = $this->parseResponse(334))) { /* 503: Error: already authenticated */ - if ($this->_code === 503) { + if ($this->code === 503) { return true; } return $error; } - $auth_str = base64_encode(chr(0) . $uid . chr(0) . $pwd); + $auth_str = base64_encode($authz . chr(0) . $uid . chr(0) . $pwd); - if (PEAR::isError($error = $this->_put($auth_str))) { + if (PEAR::isError($error = $this->put($auth_str))) { return $error; } /* 235: Authentication successful */ - if (PEAR::isError($error = $this->_parseResponse(235))) { + if (PEAR::isError($error = $this->parseResponse(235))) { return $error; } @@ -762,19 +853,18 @@ function _authPlain($uid, $pwd) /** * Send the HELO command. * - * @param string The domain name to say we are. + * @param string $domain The domain name to say we are. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access public - * @since 1.0 + * @since 1.0 */ - function helo($domain) + public function helo($domain) { - if (PEAR::isError($error = $this->_put('HELO', $domain))) { + if (PEAR::isError($error = $this->put('HELO', $domain))) { return $error; } - if (PEAR::isError($error = $this->_parseResponse(250))) { + if (PEAR::isError($error = $this->parseResponse(250))) { return $error; } @@ -785,55 +875,50 @@ function helo($domain) * Return the list of SMTP service extensions advertised by the server. * * @return array The list of SMTP service extensions. - * @access public * @since 1.3 */ - function getServiceExtensions() + public function getServiceExtensions() { - return $this->_esmtp; + return $this->esmtp; } /** * Send the MAIL FROM: command. * - * @param string $sender The sender (reverse path) to set. - * @param string $params String containing additional MAIL parameters, - * such as the NOTIFY flags defined by RFC 1891 - * or the VERP protocol. + * @param string $sender The sender (reverse path) to set. + * @param string $params String containing additional MAIL parameters, + * such as the NOTIFY flags defined by RFC 1891 + * or the VERP protocol. * - * If $params is an array, only the 'verp' option - * is supported. If 'verp' is true, the XVERP - * parameter is appended to the MAIL command. If - * the 'verp' value is a string, the full - * XVERP=value parameter is appended. + * If $params is an array, only the 'verp' option + * is supported. If 'verp' is true, the XVERP + * parameter is appended to the MAIL command. + * If the 'verp' value is a string, the full + * XVERP=value parameter is appended. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access public - * @since 1.0 + * @since 1.0 */ - function mailFrom($sender, $params = null) + public function mailFrom($sender, $params = null) { $args = "FROM:<$sender>"; /* Support the deprecated array form of $params. */ if (is_array($params) && isset($params['verp'])) { - /* XVERP */ if ($params['verp'] === true) { $args .= ' XVERP'; - - /* XVERP=something */ } elseif (trim($params['verp'])) { $args .= ' XVERP=' . $params['verp']; } - } elseif (is_string($params)) { + } elseif (is_string($params) && !empty($params)) { $args .= ' ' . $params; } - if (PEAR::isError($error = $this->_put('MAIL', $args))) { + if (PEAR::isError($error = $this->put('MAIL', $args))) { return $error; } - if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { return $error; } @@ -850,20 +935,19 @@ function mailFrom($sender, $params = null) * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. * - * @access public - * @since 1.0 + * @since 1.0 */ - function rcptTo($recipient, $params = null) + public function rcptTo($recipient, $params = null) { $args = "TO:<$recipient>"; if (is_string($params)) { $args .= ' ' . $params; } - if (PEAR::isError($error = $this->_put('RCPT', $args))) { + if (PEAR::isError($error = $this->put('RCPT', $args))) { return $error; } - if (PEAR::isError($error = $this->_parseResponse(array(250, 251), $this->pipelining))) { + if (PEAR::isError($error = $this->parseResponse(array(250, 251), $this->pipelining))) { return $error; } @@ -877,114 +961,155 @@ function rcptTo($recipient, $params = null) * easier overloading for the cases where it is desirable to * customize the quoting behavior. * - * @param string $data The message text to quote. The string must be passed + * @param string &$data The message text to quote. The string must be passed * by reference, and the text will be modified in place. * - * @access public - * @since 1.2 + * @since 1.2 */ - function quotedata(&$data) + public function quotedata(&$data) { - /* Change Unix (\n) and Mac (\r) linefeeds into - * Internet-standard CRLF (\r\n) linefeeds. */ - $data = preg_replace(array('/(?_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) { - /* Start by considering the size of the optional headers string. - * We also account for the addition 4 character "\r\n\r\n" - * separator sequence. */ - $size = (is_null($headers)) ? 0 : strlen($headers) + 4; - - if (is_resource($data)) { - $stat = fstat($data); - if ($stat === false) { - return PEAR::raiseError('Failed to get file size'); - } - $size += $stat['size']; - } else { - $size += strlen($data); - } + /* Start by considering the size of the optional headers string. We + * also account for the addition 4 character "\r\n\r\n" separator + * sequence. */ + $size = (is_null($headers)) ? 0 : strlen($headers) + 4; - if ($size >= $this->_esmtp['SIZE']) { - $this->disconnect(); - return PEAR::raiseError('Message size exceeds server limit'); + if (is_resource($data)) { + $stat = fstat($data); + if ($stat === false) { + return PEAR::raiseError('Failed to get file size'); } + $size += $stat['size']; + } else { + $size += strlen($data); + } + + /* RFC 1870, section 3, subsection 3 states "a value of zero indicates + * that no fixed maximum message size is in force". Furthermore, it + * says that if "the parameter is omitted no information is conveyed + * about the server's fixed maximum message size". */ + $limit = (isset($this->esmtp['SIZE'])) ? $this->esmtp['SIZE'] : 0; + if ($limit > 0 && $size >= $limit) { + $this->disconnect(); + return PEAR::raiseError('Message size exceeds server limit'); } /* Initiate the DATA command. */ - if (PEAR::isError($error = $this->_put('DATA'))) { + if (PEAR::isError($error = $this->put('DATA'))) { return $error; } - if (PEAR::isError($error = $this->_parseResponse(354))) { + if (PEAR::isError($error = $this->parseResponse(354))) { return $error; } /* If we have a separate headers string, send it first. */ if (!is_null($headers)) { $this->quotedata($headers); - if (PEAR::isError($result = $this->_send($headers . "\r\n\r\n"))) { + if (PEAR::isError($result = $this->send($headers . "\r\n\r\n"))) { return $result; } + + /* Subtract the headers size now that they've been sent. */ + $size -= strlen($headers) + 4; } /* Now we can send the message body data. */ if (is_resource($data)) { - /* Stream the contents of the file resource out over our socket - * connection, line by line. Each line must be run through the + /* Stream the contents of the file resource out over our socket + * connection, line by line. Each line must be run through the * quoting routine. */ - while ($line = fgets($data, 1024)) { + while (strlen($line = fread($data, 8192)) > 0) { + /* If the last character is an newline, we need to grab the + * next character to check to see if it is a period. */ + while (!feof($data)) { + $char = fread($data, 1); + $line .= $char; + if ($char != "\n") { + break; + } + } $this->quotedata($line); - if (PEAR::isError($result = $this->_send($line))) { + if (PEAR::isError($result = $this->send($line))) { return $result; } } - /* Finally, send the DATA terminator sequence. */ - if (PEAR::isError($result = $this->_send("\r\n.\r\n"))) { - return $result; - } + $last = $line; } else { - /* Just send the entire quoted string followed by the DATA - * terminator. */ - $this->quotedata($data); - if (PEAR::isError($result = $this->_send($data . "\r\n.\r\n"))) { - return $result; + /* + * Break up the data by sending one chunk (up to 512k) at a time. + * This approach reduces our peak memory usage. + */ + for ($offset = 0; $offset < $size;) { + $end = $offset + 512000; + + /* + * Ensure we don't read beyond our data size or span multiple + * lines. quotedata() can't properly handle character data + * that's split across two line break boundaries. + */ + if ($end >= $size) { + $end = $size; + } else { + for (; $end < $size; $end++) { + if ($data[$end] != "\n") { + break; + } + } + } + + /* Extract our chunk and run it through the quoting routine. */ + $chunk = substr($data, $offset, $end - $offset); + $this->quotedata($chunk); + + /* If we run into a problem along the way, abort. */ + if (PEAR::isError($result = $this->send($chunk))) { + return $result; + } + + /* Advance the offset to the end of this chunk. */ + $offset = $end; } + + $last = $chunk; + } + + /* Don't add another CRLF sequence if it's already in the data */ + $terminator = (substr($last, -2) == "\r\n" ? '' : "\r\n") . ".\r\n"; + + /* Finally, send the DATA terminator sequence. */ + if (PEAR::isError($result = $this->send($terminator))) { + return $result; } /* Verify that the data was successfully received by the server. */ - if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { return $error; } @@ -994,134 +1119,79 @@ function data($data, $headers = null) /** * Send the SEND FROM: command. * - * @param string The reverse path to send. + * @param string $path The reverse path to send. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access public - * @since 1.2.6 + * @since 1.2.6 */ - function sendFrom($path) + public function sendFrom($path) { - if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) { + if (PEAR::isError($error = $this->put('SEND', "FROM:<$path>"))) { return $error; } - if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { return $error; } return true; } - /** - * Backwards-compatibility wrapper for sendFrom(). - * - * @param string The reverse path to send. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * - * @access public - * @since 1.0 - * @deprecated 1.2.6 - */ - function send_from($path) - { - return sendFrom($path); - } - /** * Send the SOML FROM: command. * - * @param string The reverse path to send. + * @param string $path The reverse path to send. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access public - * @since 1.2.6 + * @since 1.2.6 */ - function somlFrom($path) + public function somlFrom($path) { - if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) { + if (PEAR::isError($error = $this->put('SOML', "FROM:<$path>"))) { return $error; } - if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { return $error; } return true; } - /** - * Backwards-compatibility wrapper for somlFrom(). - * - * @param string The reverse path to send. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * - * @access public - * @since 1.0 - * @deprecated 1.2.6 - */ - function soml_from($path) - { - return somlFrom($path); - } - /** * Send the SAML FROM: command. * - * @param string The reverse path to send. + * @param string $path The reverse path to send. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access public - * @since 1.2.6 + * @since 1.2.6 */ - function samlFrom($path) + public function samlFrom($path) { - if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) { + if (PEAR::isError($error = $this->put('SAML', "FROM:<$path>"))) { return $error; } - if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { return $error; } return true; } - /** - * Backwards-compatibility wrapper for samlFrom(). - * - * @param string The reverse path to send. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * - * @access public - * @since 1.0 - * @deprecated 1.2.6 - */ - function saml_from($path) - { - return samlFrom($path); - } - /** * Send the RSET command. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access public * @since 1.0 */ - function rset() + public function rset() { - if (PEAR::isError($error = $this->_put('RSET'))) { + if (PEAR::isError($error = $this->put('RSET'))) { return $error; } - if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { return $error; } @@ -1131,20 +1201,19 @@ function rset() /** * Send the VRFY command. * - * @param string The string to verify + * @param string $string The string to verify * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access public - * @since 1.0 + * @since 1.0 */ - function vrfy($string) + public function vrfy($string) { /* Note: 251 is also a valid response code */ - if (PEAR::isError($error = $this->_put('VRFY', $string))) { + if (PEAR::isError($error = $this->put('VRFY', $string))) { return $error; } - if (PEAR::isError($error = $this->_parseResponse(array(250, 252)))) { + if (PEAR::isError($error = $this->parseResponse(array(250, 252)))) { return $error; } @@ -1156,15 +1225,14 @@ function vrfy($string) * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. - * @access public - * @since 1.0 + * @since 1.0 */ - function noop() + public function noop() { - if (PEAR::isError($error = $this->_put('NOOP'))) { + if (PEAR::isError($error = $this->put('NOOP'))) { return $error; } - if (PEAR::isError($error = $this->_parseResponse(250))) { + if (PEAR::isError($error = $this->parseResponse(250))) { return $error; } @@ -1175,14 +1243,12 @@ function noop() * Backwards-compatibility method. identifySender()'s functionality is * now handled internally. * - * @return boolean This method always return true. + * @return boolean This method always return true. * - * @access public - * @since 1.0 + * @since 1.0 */ - function identifySender() + public function identifySender() { return true; } - } diff --git a/WEB-INF/lib/pear/Net/Socket.php b/WEB-INF/lib/pear/Net/Socket.php index 73bb4dd11..bf1d1bbcd 100644 --- a/WEB-INF/lib/pear/Net/Socket.php +++ b/WEB-INF/lib/pear/Net/Socket.php @@ -1,39 +1,50 @@ | -// | Chuck Hagenbuch | -// +----------------------------------------------------------------------+ -// -// $Id: Socket.php,v 1.38 2008/02/15 18:24:17 chagenbu Exp $ +/** + * Net_Socket + * + * PHP Version 4 + * + * Copyright (c) 1997-2013 The PHP Group + * + * This source file is subject to version 2.0 of the PHP license, + * that is bundled with this package in the file LICENSE, and is + * available at through the world-wide-web at + * http://www.php.net/license/2_02.txt. + * If you did not receive a copy of the PHP license and are unable to + * obtain it through the world-wide-web, please send a note to + * license@php.net so we can mail you a copy immediately. + * + * Authors: Stig Bakken + * Chuck Hagenbuch + * + * @category Net + * @package Net_Socket + * @author Stig Bakken + * @author Chuck Hagenbuch + * @copyright 1997-2003 The PHP Group + * @license http://www.php.net/license/2_02.txt PHP 2.02 + * @link http://pear.php.net/packages/Net_Socket + */ require_once 'PEAR.php'; -define('NET_SOCKET_READ', 1); +define('NET_SOCKET_READ', 1); define('NET_SOCKET_WRITE', 2); define('NET_SOCKET_ERROR', 4); /** * Generalized Socket class. * - * @version 1.1 - * @author Stig Bakken - * @author Chuck Hagenbuch + * @category Net + * @package Net_Socket + * @author Stig Bakken + * @author Chuck Hagenbuch + * @copyright 1997-2003 The PHP Group + * @license http://www.php.net/license/2_02.txt PHP 2.02 + * @link http://pear.php.net/packages/Net_Socket */ -class Net_Socket extends PEAR { - +class Net_Socket extends PEAR +{ /** * Socket file pointer. * @var resource $fp @@ -65,11 +76,11 @@ class Net_Socket extends PEAR { var $port = 0; /** - * Number of seconds to wait on socket connections before assuming + * Number of seconds to wait on socket operations before assuming * there's no more data. Defaults to no timeout. - * @var integer $timeout + * @var integer|float $timeout */ - var $timeout = false; + var $timeout = null; /** * Number of bytes to read at a time in readLine() and @@ -78,23 +89,30 @@ class Net_Socket extends PEAR { */ var $lineLength = 2048; + /** + * The string to use as a newline terminator. Usually "\r\n" or "\n". + * @var string $newline + */ + var $newline = "\r\n"; + /** * Connect to the specified port. If called when the socket is * already connected, it disconnects and connects again. * - * @param string $addr IP address or host name. - * @param integer $port TCP port number. - * @param boolean $persistent (optional) Whether the connection is - * persistent (kept open between requests - * by the web server). - * @param integer $timeout (optional) How long to wait for data. - * @param array $options See options for stream_context_create. + * @param string $addr IP address or host name (may be with protocol prefix). + * @param integer $port TCP port number. + * @param boolean $persistent (optional) Whether the connection is + * persistent (kept open between requests + * by the web server). + * @param integer $timeout (optional) Connection socket timeout. + * @param array $options See options for stream_context_create. * * @access public * - * @return boolean | PEAR_Error True on success or a PEAR_Error on failure. + * @return boolean|PEAR_Error True on success or a PEAR_Error on failure. */ - function connect($addr, $port = 0, $persistent = null, $timeout = null, $options = null) + function connect($addr, $port = 0, $persistent = null, + $timeout = null, $options = null) { if (is_resource($this->fp)) { @fclose($this->fp); @@ -103,11 +121,10 @@ function connect($addr, $port = 0, $persistent = null, $timeout = null, $options if (!$addr) { return $this->raiseError('$addr cannot be empty'); - } elseif (strspn($addr, '.0123456789') == strlen($addr) || - strstr($addr, '/') !== false) { - $this->addr = $addr; + } else if (strspn($addr, ':.0123456789') == strlen($addr)) { + $this->addr = strpos($addr, ':') !== false ? '['.$addr.']' : $addr; } else { - $this->addr = @gethostbyname($addr); + $this->addr = $addr; } $this->port = $port % 65536; @@ -116,40 +133,40 @@ function connect($addr, $port = 0, $persistent = null, $timeout = null, $options $this->persistent = $persistent; } - if ($timeout !== null) { - $this->timeout = $timeout; - } - $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen'; - $errno = 0; - $errstr = ''; + $errno = 0; + $errstr = ''; + $old_track_errors = @ini_set('track_errors', 1); + + if ($timeout <= 0) { + $timeout = @ini_get('default_socket_timeout'); + } + if ($options && function_exists('stream_context_create')) { - if ($this->timeout) { - $timeout = $this->timeout; - } else { - $timeout = 0; - } $context = stream_context_create($options); // Since PHP 5 fsockopen doesn't allow context specification if (function_exists('stream_socket_client')) { - $flags = $this->persistent ? STREAM_CLIENT_PERSISTENT : STREAM_CLIENT_CONNECT; + $flags = STREAM_CLIENT_CONNECT; + + if ($this->persistent) { + $flags = STREAM_CLIENT_PERSISTENT; + } + $addr = $this->addr . ':' . $this->port; - $fp = stream_socket_client($addr, $errno, $errstr, $timeout, $flags, $context); + $fp = stream_socket_client($addr, $errno, $errstr, + $timeout, $flags, $context); } else { - $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout, $context); + $fp = @$openfunc($this->addr, $this->port, $errno, + $errstr, $timeout, $context); } } else { - if ($this->timeout) { - $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout); - } else { - $fp = @$openfunc($this->addr, $this->port, $errno, $errstr); - } + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout); } if (!$fp) { - if ($errno == 0 && isset($php_errormsg)) { + if ($errno == 0 && !strlen($errstr) && isset($php_errormsg)) { $errstr = $php_errormsg; } @ini_set('track_errors', $old_track_errors); @@ -158,7 +175,7 @@ function connect($addr, $port = 0, $persistent = null, $timeout = null, $options @ini_set('track_errors', $old_track_errors); $this->fp = $fp; - + $this->setTimeout(); return $this->setBlocking($this->blocking); } @@ -179,6 +196,18 @@ function disconnect() return true; } + /** + * Set the newline character/sequence to use. + * + * @param string $newline Newline character(s) + * @return boolean True + */ + function setNewline($newline) + { + $this->newline = $newline; + return true; + } + /** * Find out if the socket is in blocking mode. * @@ -196,7 +225,8 @@ function isBlocking() * if there is no data available, whereas it will block until there * is data for blocking sockets. * - * @param boolean $mode True for blocking sockets, false for nonblocking. + * @param boolean $mode True for blocking sockets, false for nonblocking. + * * @access public * @return mixed true on success or a PEAR_Error instance otherwise */ @@ -207,7 +237,7 @@ function setBlocking($mode) } $this->blocking = $mode; - socket_set_blocking($this->fp, $this->blocking); + stream_set_blocking($this->fp, (int)$this->blocking); return true; } @@ -215,25 +245,40 @@ function setBlocking($mode) * Sets the timeout value on socket descriptor, * expressed in the sum of seconds and microseconds * - * @param integer $seconds Seconds. - * @param integer $microseconds Microseconds. + * @param integer $seconds Seconds. + * @param integer $microseconds Microseconds, optional. + * * @access public - * @return mixed true on success or a PEAR_Error instance otherwise + * @return mixed True on success or false on failure or + * a PEAR_Error instance when not connected */ - function setTimeout($seconds, $microseconds) + function setTimeout($seconds = null, $microseconds = null) { if (!is_resource($this->fp)) { return $this->raiseError('not connected'); } - return socket_set_timeout($this->fp, $seconds, $microseconds); + if ($seconds === null && $microseconds === null) { + $seconds = (int) $this->timeout; + $microseconds = (int) (($this->timeout - $seconds) * 1000000); + } else { + $this->timeout = $seconds + $microseconds/1000000; + } + + if ($this->timeout > 0) { + return stream_set_timeout($this->fp, (int) $seconds, (int) $microseconds); + } + else { + return false; + } } /** * Sets the file buffering size on the stream. * See php's stream_set_write_buffer for more information. * - * @param integer $size Write buffer size. + * @param integer $size Write buffer size. + * * @access public * @return mixed on success or an PEAR_Error object otherwise */ @@ -262,7 +307,8 @@ function setWriteBuffer($size) *

* * @access public - * @return mixed Array containing information about existing socket resource or a PEAR_Error instance otherwise + * @return mixed Array containing information about existing socket + * resource or a PEAR_Error instance otherwise */ function getStatus() { @@ -270,23 +316,32 @@ function getStatus() return $this->raiseError('not connected'); } - return socket_get_status($this->fp); + return stream_get_meta_data($this->fp); } /** * Get a specified line of data * + * @param int $size Reading ends when size - 1 bytes have been read, + * or a newline or an EOF (whichever comes first). + * If no size is specified, it will keep reading from + * the stream until it reaches the end of the line. + * * @access public - * @return $size bytes of data from the socket, or a PEAR_Error if - * not connected. + * @return mixed $size bytes of data from the socket, or a PEAR_Error if + * not connected. If an error occurs, FALSE is returned. */ - function gets($size) + function gets($size = null) { if (!is_resource($this->fp)) { return $this->raiseError('not connected'); } - return @fgets($this->fp, $size); + if (is_null($size)) { + return @fgets($this->fp); + } else { + return @fgets($this->fp, $size); + } } /** @@ -295,7 +350,8 @@ function gets($size) * chunk; if you know the size of the data you're getting * beforehand, this is definitely the way to go. * - * @param integer $size The number of bytes to read from the socket. + * @param integer $size The number of bytes to read from the socket. + * * @access public * @return $size bytes of data from the socket, or a PEAR_Error if * not connected. @@ -312,14 +368,16 @@ function read($size) /** * Write a specified amount of data. * - * @param string $data Data to write. - * @param integer $blocksize Amount of data to write at once. - * NULL means all at once. + * @param string $data Data to write. + * @param integer $blocksize Amount of data to write at once. + * NULL means all at once. * * @access public - * @return mixed If the socket is not connected, returns an instance of PEAR_Error - * If the write succeeds, returns the number of bytes written + * @return mixed If the socket is not connected, returns an instance of + * PEAR_Error. + * If the write succeeds, returns the number of bytes written. * If the write fails, returns false. + * If the socket times out, returns an instance of PEAR_Error. */ function write($data, $blocksize = null) { @@ -328,19 +386,47 @@ function write($data, $blocksize = null) } if (is_null($blocksize) && !OS_WINDOWS) { - return @fwrite($this->fp, $data); + $written = @fwrite($this->fp, $data); + + // Check for timeout or lost connection + if (!$written) { + $meta_data = $this->getStatus(); + + if (!is_array($meta_data)) { + return $meta_data; // PEAR_Error + } + + if (!empty($meta_data['timed_out'])) { + return $this->raiseError('timed out'); + } + } + + return $written; } else { if (is_null($blocksize)) { $blocksize = 1024; } - $pos = 0; + $pos = 0; $size = strlen($data); while ($pos < $size) { $written = @fwrite($this->fp, substr($data, $pos, $blocksize)); - if ($written === false) { - return false; + + // Check for timeout or lost connection + if (!$written) { + $meta_data = $this->getStatus(); + + if (!is_array($meta_data)) { + return $meta_data; // PEAR_Error + } + + if (!empty($meta_data['timed_out'])) { + return $this->raiseError('timed out'); + } + + return $written; } + $pos += $written; } @@ -349,10 +435,12 @@ function write($data, $blocksize = null) } /** - * Write a line of data to the socket, followed by a trailing "\r\n". + * Write a line of data to the socket, followed by a trailing newline. + * + * @param string $data Data to write * * @access public - * @return mixed fputs result, or an error + * @return mixed fwrite() result, or PEAR_Error when not connected */ function writeLine($data) { @@ -360,7 +448,7 @@ function writeLine($data) return $this->raiseError('not connected'); } - return fwrite($this->fp, $data . "\r\n"); + return fwrite($this->fp, $data . $this->newline); } /** @@ -441,7 +529,7 @@ function readString() } $string = ''; - while (($char = @fread($this->fp, 1)) != "\x00") { + while (($char = @fread($this->fp, 1)) != "\x00") { $string .= $char; } return $string; @@ -481,11 +569,13 @@ function readLine() } $line = ''; + $timeout = time() + $this->timeout; + while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) { $line .= @fgets($this->fp, $this->lineLength); if (substr($line, -1) == "\n") { - return rtrim($line, "\r\n"); + return rtrim($line, $this->newline); } } return $line; @@ -521,9 +611,9 @@ function readAll() * Runs the equivalent of the select() system call on the socket * with a timeout specified by tv_sec and tv_usec. * - * @param integer $state Which of read/write/error to check for. - * @param integer $tv_sec Number of seconds for timeout. - * @param integer $tv_usec Number of microseconds for timeout. + * @param integer $state Which of read/write/error to check for. + * @param integer $tv_sec Number of seconds for timeout. + * @param integer $tv_usec Number of microseconds for timeout. * * @access public * @return False if select fails, integer describing which of read/write/error @@ -535,8 +625,8 @@ function select($state, $tv_sec, $tv_usec = 0) return $this->raiseError('not connected'); } - $read = null; - $write = null; + $read = null; + $write = null; $except = null; if ($state & NET_SOCKET_READ) { $read[] = $this->fp; @@ -547,7 +637,8 @@ function select($state, $tv_sec, $tv_usec = 0) if ($state & NET_SOCKET_ERROR) { $except[] = $this->fp; } - if (false === ($sr = stream_select($read, $write, $except, $tv_sec, $tv_usec))) { + if (false === ($sr = stream_select($read, $write, $except, + $tv_sec, $tv_usec))) { return false; } @@ -567,15 +658,17 @@ function select($state, $tv_sec, $tv_usec = 0) /** * Turns encryption on/off on a connected socket. * - * @param bool $enabled Set this parameter to true to enable encryption - * and false to disable encryption. - * @param integer $type Type of encryption. See - * http://se.php.net/manual/en/function.stream-socket-enable-crypto.php for values. + * @param bool $enabled Set this parameter to true to enable encryption + * and false to disable encryption. + * @param integer $type Type of encryption. See stream_socket_enable_crypto() + * for values. * + * @see http://se.php.net/manual/en/function.stream-socket-enable-crypto.php * @access public - * @return false on error, true on success and 0 if there isn't enough data and the - * user should try again (non-blocking sockets only). A PEAR_Error object - * is returned if the socket is not connected + * @return false on error, true on success and 0 if there isn't enough data + * and the user should try again (non-blocking sockets only). + * A PEAR_Error object is returned if the socket is not + * connected */ function enableCrypto($enabled, $type) { @@ -585,7 +678,8 @@ function enableCrypto($enabled, $type) } return @stream_socket_enable_crypto($this->fp, $enabled, $type); } else { - return $this->raiseError('Net_Socket::enableCrypto() requires php version >= 5.1.0'); + $msg = 'Net_Socket::enableCrypto() requires php version >= 5.1.0'; + return $this->raiseError($msg); } } diff --git a/WEB-INF/lib/pear/OS/Guess.php b/WEB-INF/lib/pear/OS/Guess.php index d3f2cc764..4c9254a26 100644 --- a/WEB-INF/lib/pear/OS/Guess.php +++ b/WEB-INF/lib/pear/OS/Guess.php @@ -10,7 +10,6 @@ * @author Gregory Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Guess.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since PEAR 0.1 */ @@ -87,7 +86,7 @@ * @author Gregory Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -99,7 +98,7 @@ class OS_Guess var $release; var $extra; - function OS_Guess($uname = null) + function __construct($uname = null) { list($this->sysname, $this->release, @@ -335,4 +334,4 @@ function _matchFragment($fragment, $value) * indent-tabs-mode: nil * c-basic-offset: 4 * End: - */ \ No newline at end of file + */ diff --git a/WEB-INF/lib/pear/PEAR.php b/WEB-INF/lib/pear/PEAR.php index 2aa85259d..d661cc212 100644 --- a/WEB-INF/lib/pear/PEAR.php +++ b/WEB-INF/lib/pear/PEAR.php @@ -14,7 +14,6 @@ * @author Greg Beaver * @copyright 1997-2010 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: PEAR.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -33,8 +32,6 @@ */ define('PEAR_ERROR_EXCEPTION', 32); /**#@-*/ -define('PEAR_ZE2', (function_exists('version_compare') && - version_compare(zend_version(), "2-dev", "ge"))); if (substr(PHP_OS, 0, 3) == 'WIN') { define('OS_WINDOWS', true); @@ -78,7 +75,7 @@ * @author Greg Beaver * @copyright 1997-2006 The PHP Group * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @see PEAR_Error * @since Class available since PHP 4.0.2 @@ -136,6 +133,18 @@ class PEAR */ var $_expected_errors = array(); + /** + * List of methods that can be called both statically and non-statically. + * @var array + */ + protected static $bivalentMethods = array( + 'setErrorHandling' => true, + 'raiseError' => true, + 'throwError' => true, + 'pushErrorHandling' => true, + 'popErrorHandling' => true, + ); + /** * Constructor. Registers this object in * $_PEAR_destructor_object_list for destructor emulation if a @@ -146,7 +155,7 @@ class PEAR * @access public * @return void */ - function PEAR($error_class = null) + function __construct($error_class = null) { $classname = strtolower(get_class($this)); if ($this->_debug) { @@ -173,6 +182,18 @@ function PEAR($error_class = null) } } + /** + * Only here for backwards compatibility. + * E.g. Archive_Tar calls $this->PEAR() in its constructor. + * + * @param string $error_class Which class to use for error objects, + * defaults to PEAR_Error. + */ + public function PEAR($error_class = null) + { + self::__construct($error_class); + } + /** * Destructor (the emulated type of...). Does nothing right now, * but is included for forward compatibility, so subclass @@ -190,19 +211,44 @@ function _PEAR() { } } + public function __call($method, $arguments) + { + if (!isset(self::$bivalentMethods[$method])) { + trigger_error( + 'Call to undefined method PEAR::' . $method . '()', E_USER_ERROR + ); + } + return call_user_func_array( + array(get_class(), '_' . $method), + array_merge(array($this), $arguments) + ); + } + + public static function __callStatic($method, $arguments) + { + if (!isset(self::$bivalentMethods[$method])) { + trigger_error( + 'Call to undefined method PEAR::' . $method . '()', E_USER_ERROR + ); + } + return call_user_func_array( + array(get_class(), '_' . $method), + array_merge(array(null), $arguments) + ); + } + /** * If you have a class that's mostly/entirely static, and you need static * properties, you can use this method to simulate them. Eg. in your method(s) * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); * You MUST use a reference, or they will not persist! * - * @access public * @param string $class The calling classname, to prevent clashes * @param string $var The variable to retrieve. * @return mixed A reference to the variable. If not set it will be * auto initialised to NULL. */ - function &getStaticProperty($class, $var) + public static function &getStaticProperty($class, $var) { static $properties; if (!isset($properties[$class])) { @@ -220,12 +266,12 @@ function &getStaticProperty($class, $var) * Use this function to register a shutdown method for static * classes. * - * @access public * @param mixed $func The function name (or array of class/method) to call * @param mixed $args The arguments to pass to the function + * * @return void */ - function registerShutdownFunc($func, $args = array()) + public static function registerShutdownFunc($func, $args = array()) { // if we are called statically, there is a potential // that no shutdown func is registered. Bug #6445 @@ -244,10 +290,10 @@ function registerShutdownFunc($func, $args = array()) * only if $code is a string and * $obj->getMessage() == $code or * $code is an integer and $obj->getCode() == $code - * @access public + * * @return bool true if parameter is an error */ - function isError($data, $code = null) + public static function isError($data, $code = null) { if (!is_a($data, 'PEAR_Error')) { return false; @@ -269,6 +315,9 @@ function isError($data, $code = null) * PEAR objects. If called in an object, setErrorHandling sets * the default behaviour for that object. * + * @param object $object + * Object the method was called on (non-static mode) + * * @param int $mode * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, @@ -300,11 +349,12 @@ function isError($data, $code = null) * * @since PHP 4.0.5 */ - function setErrorHandling($mode = null, $options = null) - { - if (isset($this) && is_a($this, 'PEAR')) { - $setmode = &$this->_default_error_mode; - $setoptions = &$this->_default_error_options; + protected static function _setErrorHandling( + $object, $mode = null, $options = null + ) { + if ($object !== null) { + $setmode = &$object->_default_error_mode; + $setoptions = &$object->_default_error_options; } else { $setmode = &$GLOBALS['_PEAR_default_error_mode']; $setoptions = &$GLOBALS['_PEAR_default_error_options']; @@ -464,12 +514,12 @@ function delExpect($error_code) * @param bool $skipmsg If true, raiseError will only pass error codes, * the error message parameter will be dropped. * - * @access public * @return object a PEAR error object * @see PEAR::setErrorHandling * @since PHP 4.0.5 */ - function &raiseError($message = null, + protected static function _raiseError($object, + $message = null, $code = null, $mode = null, $options = null, @@ -487,10 +537,10 @@ function &raiseError($message = null, } if ( - isset($this) && - isset($this->_expected_errors) && - count($this->_expected_errors) > 0 && - count($exp = end($this->_expected_errors)) + $object !== null && + isset($object->_expected_errors) && + count($object->_expected_errors) > 0 && + count($exp = end($object->_expected_errors)) ) { if ($exp[0] == "*" || (is_int(reset($exp)) && in_array($code, $exp)) || @@ -503,9 +553,9 @@ function &raiseError($message = null, // No mode given, try global ones if ($mode === null) { // Class error handler - if (isset($this) && isset($this->_default_error_mode)) { - $mode = $this->_default_error_mode; - $options = $this->_default_error_options; + if ($object !== null && isset($object->_default_error_mode)) { + $mode = $object->_default_error_mode; + $options = $object->_default_error_options; // Global error handler } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { $mode = $GLOBALS['_PEAR_default_error_mode']; @@ -515,18 +565,12 @@ function &raiseError($message = null, if ($error_class !== null) { $ec = $error_class; - } elseif (isset($this) && isset($this->_error_class)) { - $ec = $this->_error_class; + } elseif ($object !== null && isset($object->_error_class)) { + $ec = $object->_error_class; } else { $ec = 'PEAR_Error'; } - if (intval(PHP_VERSION) < 5) { - // little non-eval hack to fix bug #12147 - include 'PEAR/FixPHP5PEARWarnings.php'; - return $a; - } - if ($skipmsg) { $a = new $ec($code, $mode, $options, $userinfo); } else { @@ -548,14 +592,13 @@ function &raiseError($message = null, * @param string $userinfo If you need to pass along for example debug * information, this parameter is meant for that. * - * @access public * @return object a PEAR error object * @see PEAR::raiseError */ - function &throwError($message = null, $code = null, $userinfo = null) + protected static function _throwError($object, $message = null, $code = null, $userinfo = null) { - if (isset($this) && is_a($this, 'PEAR')) { - $a = &$this->raiseError($message, $code, null, null, $userinfo); + if ($object !== null) { + $a = &$object->raiseError($message, $code, null, null, $userinfo); return $a; } @@ -563,7 +606,7 @@ function &throwError($message = null, $code = null, $userinfo = null) return $a; } - function staticPushErrorHandling($mode, $options = null) + public static function staticPushErrorHandling($mode, $options = null) { $stack = &$GLOBALS['_PEAR_error_handler_stack']; $def_mode = &$GLOBALS['_PEAR_default_error_mode']; @@ -598,7 +641,7 @@ function staticPushErrorHandling($mode, $options = null) return true; } - function staticPopErrorHandling() + public static function staticPopErrorHandling() { $stack = &$GLOBALS['_PEAR_error_handler_stack']; $setmode = &$GLOBALS['_PEAR_default_error_mode']; @@ -646,20 +689,20 @@ function staticPopErrorHandling() * * @see PEAR::setErrorHandling */ - function pushErrorHandling($mode, $options = null) + protected static function _pushErrorHandling($object, $mode, $options = null) { $stack = &$GLOBALS['_PEAR_error_handler_stack']; - if (isset($this) && is_a($this, 'PEAR')) { - $def_mode = &$this->_default_error_mode; - $def_options = &$this->_default_error_options; + if ($object !== null) { + $def_mode = &$object->_default_error_mode; + $def_options = &$object->_default_error_options; } else { $def_mode = &$GLOBALS['_PEAR_default_error_mode']; $def_options = &$GLOBALS['_PEAR_default_error_options']; } $stack[] = array($def_mode, $def_options); - if (isset($this) && is_a($this, 'PEAR')) { - $this->setErrorHandling($mode, $options); + if ($object !== null) { + $object->setErrorHandling($mode, $options); } else { PEAR::setErrorHandling($mode, $options); } @@ -674,14 +717,14 @@ function pushErrorHandling($mode, $options = null) * * @see PEAR::pushErrorHandling */ - function popErrorHandling() + protected static function _popErrorHandling($object) { $stack = &$GLOBALS['_PEAR_error_handler_stack']; array_pop($stack); list($mode, $options) = $stack[sizeof($stack) - 1]; array_pop($stack); - if (isset($this) && is_a($this, 'PEAR')) { - $this->setErrorHandling($mode, $options); + if ($object !== null) { + $object->setErrorHandling($mode, $options); } else { PEAR::setErrorHandling($mode, $options); } @@ -689,13 +732,13 @@ function popErrorHandling() } /** - * OS independant PHP extension load. Remember to take care + * OS independent PHP extension load. Remember to take care * on the correct extension name for case sensitive OSes. * * @param string $ext The extension name * @return bool Success or not on the dl() call */ - function loadExtension($ext) + public static function loadExtension($ext) { if (extension_loaded($ext)) { return true; @@ -704,8 +747,7 @@ function loadExtension($ext) // if either returns true dl() will produce a FATAL error, stop that if ( function_exists('dl') === false || - ini_get('enable_dl') != 1 || - ini_get('safe_mode') == 1 + ini_get('enable_dl') != 1 ) { return false; } @@ -726,10 +768,6 @@ function_exists('dl') === false || } } -if (PEAR_ZE2) { - include_once 'PEAR5.php'; -} - function _PEAR_call_destructors() { global $_PEAR_destructor_object_list; @@ -737,11 +775,8 @@ function _PEAR_call_destructors() sizeof($_PEAR_destructor_object_list)) { reset($_PEAR_destructor_object_list); - if (PEAR_ZE2) { - $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo'); - } else { - $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo'); - } + + $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo'); if ($destructLifoExists) { $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); @@ -788,7 +823,7 @@ function _PEAR_call_destructors() * @author Gregory Beaver * @copyright 1997-2006 The PHP Group * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/manual/en/core.pear.pear-error.php * @see PEAR::raiseError(), PEAR::throwError() * @since Class available since PHP 4.0.2 @@ -823,7 +858,7 @@ class PEAR_Error * @access public * */ - function PEAR_Error($message = 'unknown error', $code = null, + function __construct($message = 'unknown error', $code = null, $mode = null, $options = null, $userinfo = null) { if ($mode === null) { @@ -834,11 +869,7 @@ function PEAR_Error($message = 'unknown error', $code = null, $this->mode = $mode; $this->userinfo = $userinfo; - if (PEAR_ZE2) { - $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace'); - } else { - $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace'); - } + $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace'); if (!$skiptrace) { $this->backtrace = debug_backtrace(); @@ -896,6 +927,24 @@ function PEAR_Error($message = 'unknown error', $code = null, } } + /** + * Only here for backwards compatibility. + * + * Class "Cache_Error" still uses it, among others. + * + * @param string $message Message + * @param int $code Error code + * @param int $mode Error mode + * @param mixed $options See __construct() + * @param string $userinfo Additional user/debug info + */ + public function PEAR_Error( + $message = 'unknown error', $code = null, $mode = null, + $options = null, $userinfo = null + ) { + self::__construct($message, $code, $mode, $options, $userinfo); + } + /** * Get the error mode from an error object. * diff --git a/WEB-INF/lib/pear/PEAR/Autoloader.php b/WEB-INF/lib/pear/PEAR/Autoloader.php index 0ed707ec8..bcb57f65b 100644 --- a/WEB-INF/lib/pear/PEAR/Autoloader.php +++ b/WEB-INF/lib/pear/PEAR/Autoloader.php @@ -10,7 +10,6 @@ * @author Stig Bakken * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Autoloader.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader * @since File available since Release 0.1 * @deprecated File deprecated in Release 1.4.0a1 @@ -45,7 +44,7 @@ * @author Stig Bakken * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader * @since File available since Release 0.1 * @deprecated File deprecated in Release 1.4.0a1 @@ -144,7 +143,7 @@ function addAggregateObject($classname) $include_file = preg_replace('/[^a-z0-9]/i', '_', $classname); include_once $include_file; } - $obj =& new $classname; + $obj = new $classname; $methods = get_class_methods($classname); foreach ($methods as $method) { // don't import priviate methods and constructors diff --git a/WEB-INF/lib/pear/PEAR/Builder.php b/WEB-INF/lib/pear/PEAR/Builder.php index 90f3a1455..94b09f08d 100644 --- a/WEB-INF/lib/pear/PEAR/Builder.php +++ b/WEB-INF/lib/pear/PEAR/Builder.php @@ -10,7 +10,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Builder.php 313024 2011-07-06 19:51:24Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 * @@ -23,6 +22,7 @@ */ require_once 'PEAR/Common.php'; require_once 'PEAR/PackageFile.php'; +require_once 'System.php'; /** * Class to handle building (compiling) extensions. @@ -33,7 +33,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since PHP 4.0.2 * @see http://pear.php.net/manual/en/core.ppm.pear-builder.php @@ -63,9 +63,9 @@ class PEAR_Builder extends PEAR_Common * * @access public */ - function PEAR_Builder(&$ui) + function __construct(&$ui) { - parent::PEAR_Common(); + parent::__construct(); $this->setFrontendObject($ui); } @@ -79,7 +79,7 @@ function _build_win32($descfile, $callback = null) $pkg = $descfile; $descfile = $pkg->getPackageFile(); } else { - $pf = &new PEAR_PackageFile($this->config, $this->debug); + $pf = new PEAR_PackageFile($this->config, $this->debug); $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); if (PEAR::isError($pkg)) { return $pkg; @@ -242,7 +242,7 @@ function _harvestInstDir($dest_prefix, $dirname, &$built_files) */ function build($descfile, $callback = null) { - if (preg_match('/(\\/|\\\\|^)([^\\/\\\\]+)?php(.+)?$/', + if (preg_match('/(\\/|\\\\|^)([^\\/\\\\]+)?php([^\\/\\\\]+)?$/', $this->config->get('php_bin'), $matches)) { if (isset($matches[2]) && strlen($matches[2]) && trim($matches[2]) != trim($this->config->get('php_prefix'))) { @@ -279,7 +279,7 @@ function build($descfile, $callback = null) $this->addTempFile($dir); } } else { - $pf = &new PEAR_PackageFile($this->config); + $pf = new PEAR_PackageFile($this->config); $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); if (PEAR::isError($pkg)) { return $pkg; @@ -322,6 +322,16 @@ function build($descfile, $callback = null) // {{{ start of interactive part $configure_command = "$dir/configure"; + + $phpConfigName = $this->config->get('php_prefix') + . 'php-config' + . $this->config->get('php_suffix'); + $phpConfigPath = System::which($phpConfigName); + if ($phpConfigPath !== false) { + $configure_command .= ' --with-php-config=' + . $phpConfigPath; + } + $configure_options = $pkg->getConfigureOptions(); if ($configure_options) { foreach ($configure_options as $o) { @@ -375,7 +385,7 @@ function build($descfile, $callback = null) if (!file_exists($build_dir) || !is_dir($build_dir) || !chdir($build_dir)) { return $this->raiseError("could not chdir to $build_dir"); } - putenv('PHP_PEAR_VERSION=1.9.4'); + putenv('PHP_PEAR_VERSION=1.10.1'); foreach ($to_run as $cmd) { $err = $this->_runCommand($cmd, $callback); if (PEAR::isError($err)) { @@ -476,7 +486,7 @@ function _runCommand($command, $callback = null) return ($exitcode == 0); } - function log($level, $msg) + function log($level, $msg, $append_crlf = true) { if ($this->current_callback) { if ($this->debug >= $level) { @@ -484,6 +494,6 @@ function log($level, $msg) } return; } - return PEAR_Common::log($level, $msg); + return parent::log($level, $msg, $append_crlf); } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/ChannelFile.php b/WEB-INF/lib/pear/PEAR/ChannelFile.php index f2c02ab42..f993764c8 100644 --- a/WEB-INF/lib/pear/PEAR/ChannelFile.php +++ b/WEB-INF/lib/pear/PEAR/ChannelFile.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: ChannelFile.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -146,7 +145,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -194,9 +193,9 @@ class PEAR_ChannelFile */ var $_isValid = false; - function PEAR_ChannelFile() + function __construct() { - $this->_stack = &new PEAR_ErrorStack('PEAR_ChannelFile'); + $this->_stack = new PEAR_ErrorStack('PEAR_ChannelFile'); $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); $this->_isValid = false; } @@ -306,11 +305,12 @@ function toArray() /** * @param array - * @static + * * @return PEAR_ChannelFile|false false if invalid */ - function &fromArray($data, $compatibility = false, $stackClass = 'PEAR_ErrorStack') - { + public static function &fromArray( + $data, $compatibility = false, $stackClass = 'PEAR_ErrorStack' + ) { $a = new PEAR_ChannelFile($compatibility, $stackClass); $a->_fromArray($data); if (!$a->validate()) { @@ -322,13 +322,14 @@ function &fromArray($data, $compatibility = false, $stackClass = 'PEAR_ErrorStac /** * Unlike {@link fromArray()} this does not do any validation + * * @param array - * @static + * * @return PEAR_ChannelFile */ - function &fromArrayWithErrors($data, $compatibility = false, - $stackClass = 'PEAR_ErrorStack') - { + public static function &fromArrayWithErrors( + $data, $compatibility = false, $stackClass = 'PEAR_ErrorStack' + ) { $a = new PEAR_ChannelFile($compatibility, $stackClass); $a->_fromArray($data); return $a; @@ -1501,7 +1502,7 @@ function &getValidationObject($package = false) if (isset($this->_channelInfo['validatepackage'])) { if ($package == $this->_channelInfo['validatepackage']) { // channel validation packages are always validated by PEAR_Validate - $val = &new PEAR_Validate; + $val = new PEAR_Validate; return $val; } @@ -1513,7 +1514,7 @@ function &getValidationObject($package = false) $this->_channelInfo['validatepackage']['_content']) . '.php'; $vclass = str_replace('.', '_', $this->_channelInfo['validatepackage']['_content']); - $val = &new $vclass; + $val = new $vclass; } else { $a = false; return $a; @@ -1521,10 +1522,10 @@ function &getValidationObject($package = false) } else { $vclass = str_replace('.', '_', $this->_channelInfo['validatepackage']['_content']); - $val = &new $vclass; + $val = new $vclass; } } else { - $val = &new PEAR_Validate; + $val = new PEAR_Validate; } return $val; @@ -1556,4 +1557,4 @@ function lastModified() return time(); } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/ChannelFile/Parser.php b/WEB-INF/lib/pear/PEAR/ChannelFile/Parser.php index e630ace22..a27e8fd06 100644 --- a/WEB-INF/lib/pear/PEAR/ChannelFile/Parser.php +++ b/WEB-INF/lib/pear/PEAR/ChannelFile/Parser.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Parser.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -26,7 +25,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ diff --git a/WEB-INF/lib/pear/PEAR/Command.php b/WEB-INF/lib/pear/PEAR/Command.php index db39b8f36..9ec55507d 100644 --- a/WEB-INF/lib/pear/PEAR/Command.php +++ b/WEB-INF/lib/pear/PEAR/Command.php @@ -10,7 +10,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Command.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -94,7 +93,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -109,11 +108,8 @@ class PEAR_Command * @param object $config Instance of PEAR_Config object * * @return object the command object or a PEAR error - * - * @access public - * @static */ - function &factory($command, &$config) + public static function &factory($command, &$config) { if (empty($GLOBALS['_PEAR_Command_commandlist'])) { PEAR_Command::registerCommands(); @@ -134,13 +130,13 @@ function &factory($command, &$config) return $a; } $ui =& PEAR_Command::getFrontendObject(); - $obj = &new $class($ui, $config); + $obj = new $class($ui, $config); return $obj; } // }}} // {{{ & getObject() - function &getObject($command) + public static function &getObject($command) { $class = $GLOBALS['_PEAR_Command_commandlist'][$command]; if (!class_exists($class)) { @@ -151,7 +147,7 @@ function &getObject($command) } $ui =& PEAR_Command::getFrontendObject(); $config = &PEAR_Config::singleton(); - $obj = &new $class($ui, $config); + $obj = new $class($ui, $config); return $obj; } @@ -162,9 +158,8 @@ function &getObject($command) * Get instance of frontend object. * * @return object|PEAR_Error - * @static */ - function &getFrontendObject() + public static function &getFrontendObject() { $a = &PEAR_Frontend::singleton(); return $a; @@ -179,9 +174,8 @@ function &getFrontendObject() * @param string $uiclass Name of class implementing the frontend * * @return object the frontend object, or a PEAR error - * @static */ - function &setFrontendClass($uiclass) + public static function &setFrontendClass($uiclass) { $a = &PEAR_Frontend::setFrontendClass($uiclass); return $a; @@ -196,9 +190,8 @@ function &setFrontendClass($uiclass) * @param string $uitype Name of the frontend type (for example "CLI") * * @return object the frontend object, or a PEAR error - * @static */ - function setFrontendType($uitype) + public static function setFrontendType($uitype) { $uiclass = 'PEAR_Frontend_' . $uitype; return PEAR_Command::setFrontendClass($uiclass); @@ -221,11 +214,8 @@ function setFrontendType($uitype) * included. * * @return bool TRUE on success, a PEAR error on failure - * - * @access public - * @static */ - function registerCommands($merge = false, $dir = null) + public static function registerCommands($merge = false, $dir = null) { $parser = new PEAR_XMLParser; if ($dir === null) { @@ -305,11 +295,8 @@ function registerCommands($merge = false, $dir = null) * classes implement them. * * @return array command => implementing class - * - * @access public - * @static */ - function getCommands() + public static function getCommands() { if (empty($GLOBALS['_PEAR_Command_commandlist'])) { PEAR_Command::registerCommands(); @@ -324,11 +311,8 @@ function getCommands() * Get the list of command shortcuts. * * @return array shortcut => command - * - * @access public - * @static */ - function getShortcuts() + public static function getShortcuts() { if (empty($GLOBALS['_PEAR_Command_shortcuts'])) { PEAR_Command::registerCommands(); @@ -347,11 +331,8 @@ function getShortcuts() * @param array $long_args (reference) long getopt format * * @return void - * - * @access public - * @static */ - function getGetoptArgs($command, &$short_args, &$long_args) + public static function getGetoptArgs($command, &$short_args, &$long_args) { if (empty($GLOBALS['_PEAR_Command_commandlist'])) { PEAR_Command::registerCommands(); @@ -375,11 +356,8 @@ function getGetoptArgs($command, &$short_args, &$long_args) * @param string $command Name of the command * * @return string command description - * - * @access public - * @static */ - function getDescription($command) + public static function getDescription($command) { if (!isset($GLOBALS['_PEAR_Command_commanddesc'][$command])) { return null; @@ -394,11 +372,8 @@ function getDescription($command) * Get help for command. * * @param string $command Name of the command to return help for - * - * @access public - * @static */ - function getHelp($command) + public static function getHelp($command) { $cmds = PEAR_Command::getCommands(); if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) { diff --git a/WEB-INF/lib/pear/PEAR/Command/Auth.php b/WEB-INF/lib/pear/PEAR/Command/Auth.php index 63cd152b9..aa021ec26 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Auth.php +++ b/WEB-INF/lib/pear/PEAR/Command/Auth.php @@ -10,7 +10,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Auth.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 * @deprecated since 1.8.0alpha1 @@ -30,7 +29,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 * @deprecated since 1.8.0alpha1 @@ -74,8 +73,8 @@ class PEAR_Command_Auth extends PEAR_Command_Channels * * @access public */ - function PEAR_Command_Auth(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR_Command_Channels($ui, $config); + parent::__construct($ui, $config); } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Command/Build.php b/WEB-INF/lib/pear/PEAR/Command/Build.php index 1de732024..7f851932d 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Build.php +++ b/WEB-INF/lib/pear/PEAR/Command/Build.php @@ -11,7 +11,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Build.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -31,7 +30,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -53,9 +52,9 @@ class PEAR_Command_Build extends PEAR_Command_Common * * @access public */ - function PEAR_Command_Build(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR_Command_Common($ui, $config); + parent::__construct($ui, $config); } function doBuild($command, $options, $params) @@ -65,7 +64,7 @@ function doBuild($command, $options, $params) $params[0] = 'package.xml'; } - $builder = &new PEAR_Builder($this->ui); + $builder = new PEAR_Builder($this->ui); $this->debug = $this->config->get('verbose'); $err = $builder->build($params[0], array(&$this, 'buildCallback')); if (PEAR::isError($err)) { @@ -82,4 +81,4 @@ function buildCallback($what, $data) $this->ui->outputData(rtrim($data), 'build'); } } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Command/Channels.php b/WEB-INF/lib/pear/PEAR/Command/Channels.php index fcf01b503..690483d1d 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Channels.php +++ b/WEB-INF/lib/pear/PEAR/Command/Channels.php @@ -12,7 +12,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Channels.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -32,7 +31,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -167,9 +166,9 @@ class PEAR_Command_Channels extends PEAR_Command_Common * * @access public */ - function PEAR_Command_Channels(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR_Command_Common($ui, $config); + parent::__construct($ui, $config); } function _sortChannels($a, $b) @@ -695,7 +694,7 @@ function doAlias($command, $options, $params) 'already aliased to "' . strtolower($params[1]) . '", cannot re-alias'); } - $chan = &$reg->getChannel($params[0]); + $chan = $reg->getChannel($params[0]); if (PEAR::isError($chan)) { return $this->raiseError('Corrupt registry? Error retrieving channel "' . $params[0] . '" information (' . $chan->getMessage() . ')'); @@ -880,4 +879,4 @@ function doLogout($command, $options, $params) $this->config->store(); return true; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Command/Common.php b/WEB-INF/lib/pear/PEAR/Command/Common.php index 279a71662..4be537629 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Common.php +++ b/WEB-INF/lib/pear/PEAR/Command/Common.php @@ -10,7 +10,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -29,7 +28,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -81,9 +80,9 @@ class PEAR_Command_Common extends PEAR * * @access public */ - function PEAR_Command_Common(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR(); + parent::__construct(); $this->config = &$config; $this->ui = &$ui; } @@ -270,4 +269,4 @@ function run($command, $options, $params) return $this->$func($command, $options, $params); } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Command/Config.php b/WEB-INF/lib/pear/PEAR/Command/Config.php index a761b277f..705a7cbb8 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Config.php +++ b/WEB-INF/lib/pear/PEAR/Command/Config.php @@ -10,7 +10,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Config.php 313024 2011-07-06 19:51:24Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -29,7 +28,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -133,9 +132,9 @@ class PEAR_Command_Config extends PEAR_Command_Common * * @access public */ - function PEAR_Command_Config(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR_Command_Common($ui, $config); + parent::__construct($ui, $config); } function doConfigShow($command, $options, $params) @@ -338,7 +337,7 @@ function doConfigCreate($command, $options, $params) } $params[1] = realpath($params[1]); - $config = &new PEAR_Config($params[1], '#no#system#config#', false, false); + $config = new PEAR_Config($params[1], '#no#system#config#', false, false); if ($root{strlen($root) - 1} == '/') { $root = substr($root, 0, strlen($root) - 1); } @@ -355,6 +354,7 @@ function doConfigCreate($command, $options, $params) $config->set('download_dir', $windows ? "$root\\pear\\download" : "$root/pear/download"); $config->set('temp_dir', $windows ? "$root\\pear\\temp" : "$root/pear/temp"); $config->set('bin_dir', $windows ? "$root\\pear" : "$root/pear"); + $config->set('man_dir', $windows ? "$root\\pear\\man" : "$root/pear/man"); $config->writeConfigFile(); $this->_showConfig($config); $this->ui->outputData('Successfully created default configuration file "' . $params[1] . '"', @@ -411,4 +411,4 @@ function _checkLayer($layer = null) return false; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Command/Install.php b/WEB-INF/lib/pear/PEAR/Command/Install.php index c035f6d20..9d572eda8 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Install.php +++ b/WEB-INF/lib/pear/PEAR/Command/Install.php @@ -10,7 +10,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Install.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -30,7 +29,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -313,9 +312,9 @@ class PEAR_Command_Install extends PEAR_Command_Common * * @access public */ - function PEAR_Command_Install(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR_Command_Common($ui, $config); + parent::__construct($ui, $config); } // }}} @@ -328,7 +327,7 @@ function &getDownloader(&$ui, $options, &$config) if (!class_exists('PEAR_Downloader')) { require_once 'PEAR/Downloader.php'; } - $a = &new PEAR_Downloader($ui, $options, $config); + $a = new PEAR_Downloader($ui, $options, $config); return $a; } @@ -340,7 +339,7 @@ function &getInstaller(&$ui) if (!class_exists('PEAR_Installer')) { require_once 'PEAR/Installer.php'; } - $a = &new PEAR_Installer($ui); + $a = new PEAR_Installer($ui); return $a; } @@ -468,7 +467,7 @@ function _parseIni($filename) $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; $zend_extension_line = 'zend_extension' . $debug . $ts; $all = @file($filename); - if (!$all) { + if ($all === false) { return PEAR::raiseError('php.ini "' . $filename .'" could not be read'); } $zend_extensions = $extensions = array(); @@ -556,7 +555,13 @@ function doInstall($command, $options, $params) $packrootphp_dir = $this->installer->_prependPath( $this->config->get('php_dir', null, 'pear.php.net'), $options['packagingroot']); - $instreg = new PEAR_Registry($packrootphp_dir); // other instreg! + $metadata_dir = $this->config->get('metadata_dir', null, 'pear.php.net'); + if ($metadata_dir) { + $metadata_dir = $this->installer->_prependPath( + $metadata_dir, + $options['packagingroot']); + } + $instreg = new PEAR_Registry($packrootphp_dir, false, false, $metadata_dir); // other instreg! if ($this->config->get('verbose') > 2) { $this->ui->outputData('using package root: ' . $options['packagingroot']); @@ -768,17 +773,13 @@ function doInstall($command, $options, $params) if ($param->getPackageType() == 'extsrc' || $param->getPackageType() == 'extbin') { $exttype = 'extension'; + $extpath = $pinfo[1]['basename']; } else { - ob_start(); - phpinfo(INFO_GENERAL); - $info = ob_get_contents(); - ob_end_clean(); - $debug = function_exists('leak') ? '_debug' : ''; - $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : ''; - $exttype = 'zend_extension' . $debug . $ts; + $exttype = 'zend_extension'; + $extpath = $atts['installed_as']; } $extrainfo[] = 'You should add "' . $exttype . '=' . - $pinfo[1]['basename'] . '" to php.ini'; + $extpath . '" to php.ini'; } else { $extrainfo[] = 'Extension ' . $instpkg->getProvidesExtension() . ' enabled in php.ini'; @@ -1136,7 +1137,7 @@ function doBundle($command, $options, $params) $dest .= DIRECTORY_SEPARATOR . $pkgname; $orig = $pkgname . '-' . $pkgversion; - $tar = &new Archive_Tar($pkgfile->getArchiveFile()); + $tar = new Archive_Tar($pkgfile->getArchiveFile()); if (!$tar->extractModify($dest, $orig)) { return $this->raiseError('unable to unpack ' . $pkgfile->getArchiveFile()); } @@ -1198,7 +1199,7 @@ function _filterUptodatePackages($packages, $command) if (!isset($latestReleases[$channel])) { // fill in cache for this channel - $chan = &$reg->getChannel($channel); + $chan = $reg->getChannel($channel); if (PEAR::isError($chan)) { return $this->raiseError($chan); } @@ -1265,4 +1266,4 @@ function _filterUptodatePackages($packages, $command) return $ret; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Command/Mirror.php b/WEB-INF/lib/pear/PEAR/Command/Mirror.php index 4d157c6b8..bae7ad13e 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Mirror.php +++ b/WEB-INF/lib/pear/PEAR/Command/Mirror.php @@ -9,7 +9,6 @@ * @author Alexander Merz * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Mirror.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.2.0 */ @@ -27,7 +26,7 @@ * @author Alexander Merz * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.2.0 */ @@ -60,9 +59,9 @@ class PEAR_Command_Mirror extends PEAR_Command_Common * @param object PEAR_Frontend a reference to an frontend * @param object PEAR_Config a reference to the configuration data */ - function PEAR_Command_Mirror(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR_Command_Common($ui, $config); + parent::__construct($ui, $config); } /** @@ -82,7 +81,7 @@ function &factory($a) * @param string $command the command * @param array $options the command options before the command * @param array $params the stuff after the command name - * @return bool true if succesful + * @return bool true if successful * @throw PEAR_Error */ function doDownloadAll($command, $options, $params) @@ -136,4 +135,4 @@ function doDownloadAll($command, $options, $params) return true; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Command/Package.php b/WEB-INF/lib/pear/PEAR/Command/Package.php index 81df7bf69..c62948f1e 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Package.php +++ b/WEB-INF/lib/pear/PEAR/Command/Package.php @@ -12,7 +12,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Package.php 313024 2011-07-06 19:51:24Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -283,9 +282,9 @@ class PEAR_Command_Package extends PEAR_Command_Common * * @access public */ - function PEAR_Command_Package(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR_Command_Common($ui, $config); + parent::__construct($ui, $config); } function _displayValidationResults($err, $warn, $strict = false) @@ -310,7 +309,7 @@ function &getPackager() if (!class_exists('PEAR_Packager')) { require_once 'PEAR/Packager.php'; } - $a = &new PEAR_Packager; + $a = new PEAR_Packager; return $a; } @@ -322,7 +321,7 @@ function &getPackageFile($config, $debug = false) if (!class_exists('PEAR_PackageFile')) { require_once 'PEAR/PackageFile.php'; } - $a = &new PEAR_PackageFile($config, $debug); + $a = new PEAR_PackageFile($config, $debug); $common = new PEAR_Common; $common->ui = $this->ui; $a->setLogger($common); @@ -372,7 +371,7 @@ function doPackageValidate($command, $options, $params) $info = $obj->fromPackageFile($params[0], PEAR_VALIDATE_NORMAL); } else { $archive = $info->getArchiveFile(); - $tar = &new Archive_Tar($archive); + $tar = new Archive_Tar($archive); $tar->extract(dirname($info->getPackageFile())); $info->setPackageFile(dirname($info->getPackageFile()) . DIRECTORY_SEPARATOR . $info->getPackage() . '-' . $info->getVersion() . DIRECTORY_SEPARATOR . @@ -467,7 +466,7 @@ function doSvnTag($command, $options, $params) 'name' => 'modified', 'type' => 'yesno', 'default' => 'no', - 'prompt' => 'You have files in your SVN checkout (' . $path['from'] . ') that have been modified but not commited, do you still want to tag ' . $version . '?', + 'prompt' => 'You have files in your SVN checkout (' . $path['from'] . ') that have been modified but not committed, do you still want to tag ' . $version . '?', )); $answers = $this->ui->confirmDialog($params); @@ -881,7 +880,7 @@ function doPackageDependencies($command, $options, $params) ); foreach ($deps as $type => $subd) { $req = ($type == 'required') ? 'Yes' : 'No'; - if ($type == 'group') { + if ($type == 'group' && isset($subd['attribs']['name'])) { $group = $subd['attribs']['name']; } else { $group = ''; @@ -1031,7 +1030,7 @@ function &getInstaller(&$ui) if (!class_exists('PEAR_Installer')) { require_once 'PEAR/Installer.php'; } - $a = &new PEAR_Installer($ui); + $a = new PEAR_Installer($ui); return $a; } @@ -1048,7 +1047,7 @@ function &getCommandPackaging(&$ui, &$config) } if (class_exists('PEAR_Command_Packaging')) { - $a = &new PEAR_Command_Packaging($ui, $config); + $a = new PEAR_Command_Packaging($ui, $config); } else { $a = null; } @@ -1121,4 +1120,4 @@ function doConvert($command, $options, $params) $this->ui->outputData('Wrote new version 2.0 package.xml to "' . $saved . '"'); return true; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Command/Pickle.php b/WEB-INF/lib/pear/PEAR/Command/Pickle.php index 87aa25ea3..af6079b69 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Pickle.php +++ b/WEB-INF/lib/pear/PEAR/Command/Pickle.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 2005-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Pickle.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.1 */ @@ -27,7 +26,7 @@ * @author Greg Beaver * @copyright 2005-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.1 */ @@ -76,9 +75,9 @@ class PEAR_Command_Pickle extends PEAR_Command_Common * * @access public */ - function PEAR_Command_Pickle(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR_Command_Common($ui, $config); + parent::__construct($ui, $config); } /** @@ -92,7 +91,7 @@ function &getPackager() require_once 'PEAR/Packager.php'; } - $a = &new PEAR_Packager; + $a = new PEAR_Packager; return $a; } @@ -114,7 +113,7 @@ function &getPackageFile($config, $debug = false) require_once 'PEAR/PackageFile.php'; } - $a = &new PEAR_PackageFile($config, $debug); + $a = new PEAR_PackageFile($config, $debug); $common = new PEAR_Common; $common->ui = $this->ui; $a->setLogger($common); @@ -418,4 +417,4 @@ function _convertPackage($packagexml) $gen = &$pf->getDefaultGenerator(); $gen->toPackageFile('.'); } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Command/Registry.php b/WEB-INF/lib/pear/PEAR/Command/Registry.php index 4304db5dd..37ee48bea 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Registry.php +++ b/WEB-INF/lib/pear/PEAR/Command/Registry.php @@ -10,7 +10,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Registry.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -29,7 +28,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -98,9 +97,9 @@ class PEAR_Command_Registry extends PEAR_Command_Common * * @access public */ - function PEAR_Command_Registry(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR_Command_Common($ui, $config); + parent::__construct($ui, $config); } function _sortinfo($a, $b) @@ -261,7 +260,7 @@ function doFileList($command, $options, $params) require_once 'PEAR/PackageFile.php'; } - $pkg = &new PEAR_PackageFile($this->config, $this->_debug); + $pkg = new PEAR_PackageFile($this->config, $this->_debug); PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); $info = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); PEAR::staticPopErrorHandling(); @@ -435,7 +434,7 @@ function doInfo($command, $options, $params) require_once 'PEAR/PackageFile.php'; } - $pkg = &new PEAR_PackageFile($this->config, $this->_debug); + $pkg = new PEAR_PackageFile($this->config, $this->_debug); PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); $obj = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL); PEAR::staticPopErrorHandling(); @@ -1142,4 +1141,4 @@ function _doInfo2($command, $options, $params, &$obj, $installed) $data['raw'] = $obj->getArray(); // no validation needed $this->ui->outputData($data, 'package-info'); } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Command/Remote.php b/WEB-INF/lib/pear/PEAR/Command/Remote.php index 74478d83c..f73db24f8 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Remote.php +++ b/WEB-INF/lib/pear/PEAR/Command/Remote.php @@ -11,7 +11,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Remote.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -31,7 +30,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -155,9 +154,9 @@ class PEAR_Command_Remote extends PEAR_Command_Common * * @access public */ - function PEAR_Command_Remote(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR_Command_Common($ui, $config); + parent::__construct($ui, $config); } function _checkChannelForStatus($channel, $chan) @@ -579,7 +578,7 @@ function &getDownloader($options) if (!class_exists('PEAR_Downloader')) { require_once 'PEAR/Downloader.php'; } - $a = &new PEAR_Downloader($this->ui, $options, $this->config); + $a = new PEAR_Downloader($this->ui, $options, $this->config); return $a; } @@ -668,13 +667,13 @@ function doListUpgrades($command, $options, $params) $preferred_mirror = $this->config->get('preferred_mirror'); if ($chan->supportsREST($preferred_mirror) && ( - //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) || - ($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) + ($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror)) + || ($base = $chan->getBaseURL('REST1.0', $preferred_mirror)) ) ) { if ($base2) { - $rest = &$this->config->getREST('1.4', array()); + $rest = &$this->config->getREST('1.3', array()); $base = $base2; } else { $rest = &$this->config->getREST('1.0', array()); @@ -807,4 +806,4 @@ function doClearCache($command, $options, $params) $this->ui->outputData(rtrim($output), $command); return $num; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Command/Test.php b/WEB-INF/lib/pear/PEAR/Command/Test.php index a757d9e57..a59b1cf81 100644 --- a/WEB-INF/lib/pear/PEAR/Command/Test.php +++ b/WEB-INF/lib/pear/PEAR/Command/Test.php @@ -11,7 +11,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Test.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -31,7 +30,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -87,6 +86,10 @@ class PEAR_Command_Test extends PEAR_Command_Common 'shortopt' => 'x', 'doc' => 'Generate a code coverage report (requires Xdebug 2.0.0+)', ), + 'showdiff' => array( + 'shortopt' => 'd', + 'doc' => 'Output diff on test failure', + ), ), 'doc' => '[testfile|dir ...] Run regression tests with PHP\'s regression testing script (run-tests.php).', @@ -100,9 +103,9 @@ class PEAR_Command_Test extends PEAR_Command_Common * * @access public */ - function PEAR_Command_Test(&$ui, &$config) + function __construct(&$ui, &$config) { - parent::PEAR_Command_Common($ui, $config); + parent::__construct($ui, $config); } function doRunTests($command, $options, $params) @@ -332,6 +335,9 @@ function doRunTests($command, $options, $params) } } - return true; + if (count($failed) == 0) { + return true; + } + return $this->raiseError('Some tests failed'); } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Common.php b/WEB-INF/lib/pear/PEAR/Common.php index 3a8c7e80d..5fe76ad18 100644 --- a/WEB-INF/lib/pear/PEAR/Common.php +++ b/WEB-INF/lib/pear/PEAR/Common.php @@ -11,7 +11,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1.0 * @deprecated File deprecated since Release 1.4.0a1 @@ -118,7 +117,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 * @deprecated This class will disappear, and its components will be spread @@ -165,9 +164,9 @@ class PEAR_Common extends PEAR * * @access public */ - function PEAR_Common() + function __construct() { - parent::PEAR(); + parent::__construct(); $this->config = &PEAR_Config::singleton(); $this->debug = $this->config->get('verbose'); } @@ -241,11 +240,8 @@ function mkDirHier($dir) * @param string $msg message to write to the log * * @return void - * - * @access public - * @static */ - function log($level, $msg, $append_crlf = true) + public function log($level, $msg, $append_crlf = true) { if ($this->debug >= $level) { if (!class_exists('PEAR_Frontend')) { @@ -325,9 +321,8 @@ function betterStates($state, $include = false) * Get the valid roles for a PEAR package maintainer * * @return array - * @static */ - function getUserRoles() + public static function getUserRoles() { return $GLOBALS['_PEAR_Common_maintainer_roles']; } @@ -336,9 +331,8 @@ function getUserRoles() * Get the valid package release states of packages * * @return array - * @static */ - function getReleaseStates() + public static function getReleaseStates() { return $GLOBALS['_PEAR_Common_release_states']; } @@ -347,9 +341,8 @@ function getReleaseStates() * Get the implemented dependency types (php, ext, pkg etc.) * * @return array - * @static */ - function getDependencyTypes() + public static function getDependencyTypes() { return $GLOBALS['_PEAR_Common_dependency_types']; } @@ -358,9 +351,8 @@ function getDependencyTypes() * Get the implemented dependency relations (has, lt, ge etc.) * * @return array - * @static */ - function getDependencyRelations() + public static function getDependencyRelations() { return $GLOBALS['_PEAR_Common_dependency_relations']; } @@ -369,9 +361,8 @@ function getDependencyRelations() * Get the implemented file roles * * @return array - * @static */ - function getFileRoles() + public static function getFileRoles() { return $GLOBALS['_PEAR_Common_file_roles']; } @@ -380,9 +371,8 @@ function getFileRoles() * Get the implemented file replacement types in * * @return array - * @static */ - function getReplacementTypes() + public static function getReplacementTypes() { return $GLOBALS['_PEAR_Common_replacement_types']; } @@ -391,9 +381,8 @@ function getReplacementTypes() * Get the implemented file replacement types in * * @return array - * @static */ - function getProvideTypes() + public static function getProvideTypes() { return $GLOBALS['_PEAR_Common_provide_types']; } @@ -402,9 +391,8 @@ function getProvideTypes() * Get the implemented file replacement types in * * @return array - * @static */ - function getScriptPhases() + public static function getScriptPhases() { return $GLOBALS['_PEAR_Common_script_phases']; } @@ -440,9 +428,8 @@ function validPackageVersion($ver) /** * @param string $path relative or absolute include path * @return boolean - * @static */ - function isIncludeable($path) + public static function isIncludeable($path) { if (file_exists($path) && is_readable($path)) { return true; @@ -489,7 +476,7 @@ function _postProcessChecks($pf) */ function infoFromTgzFile($file) { - $packagefile = &new PEAR_PackageFile($this->config); + $packagefile = new PEAR_PackageFile($this->config); $pf = &$packagefile->fromTgzFile($file, PEAR_VALIDATE_NORMAL); return $this->_postProcessChecks($pf); } @@ -508,7 +495,7 @@ function infoFromTgzFile($file) */ function infoFromDescriptionFile($descfile) { - $packagefile = &new PEAR_PackageFile($this->config); + $packagefile = new PEAR_PackageFile($this->config); $pf = &$packagefile->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL); return $this->_postProcessChecks($pf); } @@ -527,7 +514,7 @@ function infoFromDescriptionFile($descfile) */ function infoFromString($data) { - $packagefile = &new PEAR_PackageFile($this->config); + $packagefile = new PEAR_PackageFile($this->config); $pf = &$packagefile->fromXmlString($data, PEAR_VALIDATE_NORMAL, false); return $this->_postProcessChecks($pf); } @@ -571,7 +558,7 @@ function _postProcessValidPackagexml(&$pf) function infoFromAny($info) { if (is_string($info) && file_exists($info)) { - $packagefile = &new PEAR_PackageFile($this->config); + $packagefile = new PEAR_PackageFile($this->config); $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL); if (PEAR::isError($pf)) { $errs = $pf->getUserinfo(); @@ -604,7 +591,7 @@ function infoFromAny($info) function xmlFromInfo($pkginfo) { $config = &PEAR_Config::singleton(); - $packagefile = &new PEAR_PackageFile($config); + $packagefile = new PEAR_PackageFile($config); $pf = &$packagefile->fromArray($pkginfo); $gen = &$pf->getDefaultGenerator(); return $gen->toXml(PEAR_VALIDATE_PACKAGING); @@ -626,7 +613,7 @@ function xmlFromInfo($pkginfo) function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '') { $config = &PEAR_Config::singleton(); - $packagefile = &new PEAR_PackageFile($config); + $packagefile = new PEAR_PackageFile($config); PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); if (strpos($info, 'fromXmlString($info, PEAR_VALIDATE_NORMAL, ''); @@ -814,24 +801,38 @@ function detectDependencies($any, $status_callback = null) * @param string $save_dir (optional) directory to save file in * @param mixed $callback (optional) function/method to call for status * updates - * - * @return string Returns the full path of the downloaded file or a PEAR - * error on failure. If the error is caused by - * socket-related errors, the error object will - * have the fsockopen error code available through - * getCode(). + * @param false|string|array $lastmodified header values to check against + * for caching + * use false to return the header + * values from this download + * @param false|array $accept Accept headers to send + * @param false|string $channel Channel to use for retrieving + * authentication + * + * @return mixed Returns the full path of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * If $lastmodified was given and the there are no changes, + * boolean false is returned. * * @access public - * @deprecated in favor of PEAR_Downloader::downloadHttp() */ - function downloadHttp($url, &$ui, $save_dir = '.', $callback = null) - { + function downloadHttp( + $url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null, + $accept = false, $channel = false + ) { if (!class_exists('PEAR_Downloader')) { require_once 'PEAR/Downloader.php'; } - return PEAR_Downloader::downloadHttp($url, $ui, $save_dir, $callback); + return PEAR_Downloader::_downloadHttp( + $this, $url, $ui, $save_dir, $callback, $lastmodified, + $accept, $channel + ); } } require_once 'PEAR/Config.php'; -require_once 'PEAR/PackageFile.php'; \ No newline at end of file +require_once 'PEAR/PackageFile.php'; diff --git a/WEB-INF/lib/pear/PEAR/Config.php b/WEB-INF/lib/pear/PEAR/Config.php index 86a7db3f3..3856acb10 100644 --- a/WEB-INF/lib/pear/PEAR/Config.php +++ b/WEB-INF/lib/pear/PEAR/Config.php @@ -10,7 +10,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Config.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -87,6 +86,13 @@ } } +// Default for metadata_dir +if (getenv('PHP_PEAR_METADATA_DIR')) { + define('PEAR_CONFIG_DEFAULT_METADATA_DIR', getenv('PHP_PEAR_METADATA_DIR')); +} else { + define('PEAR_CONFIG_DEFAULT_METADATA_DIR', ''); +} + // Default for ext_dir if (getenv('PHP_PEAR_EXTENSION_DIR')) { define('PEAR_CONFIG_DEFAULT_EXT_DIR', getenv('PHP_PEAR_EXTENSION_DIR')); @@ -142,6 +148,18 @@ $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'www'); } +// Default for man_dir +if (getenv('PHP_PEAR_MAN_DIR')) { + define('PEAR_CONFIG_DEFAULT_MAN_DIR', getenv('PHP_PEAR_MAN_DIR')); +} else { + if (defined('PHP_MANDIR')) { // Added in PHP5.3.7 + define('PEAR_CONFIG_DEFAULT_MAN_DIR', PHP_MANDIR); + } else { + define('PEAR_CONFIG_DEFAULT_MAN_DIR', PHP_PREFIX . DIRECTORY_SEPARATOR . + 'local' . DIRECTORY_SEPARATOR .'man'); + } +} + // Default for test_dir if (getenv('PHP_PEAR_TEST_DIR')) { define('PEAR_CONFIG_DEFAULT_TEST_DIR', getenv('PHP_PEAR_TEST_DIR')); @@ -246,7 +264,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -440,6 +458,13 @@ class PEAR_Config extends PEAR 'prompt' => 'PEAR www files directory', 'group' => 'File Locations (Advanced)', ), + 'man_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_MAN_DIR, + 'doc' => 'directory where unix manual pages are installed', + 'prompt' => 'Systems manpage files directory', + 'group' => 'File Locations (Advanced)', + ), 'test_dir' => array( 'type' => 'directory', 'default' => PEAR_CONFIG_DEFAULT_TEST_DIR, @@ -496,6 +521,13 @@ class PEAR_Config extends PEAR 'prompt' => 'php.ini location', 'group' => 'File Locations (Advanced)', ), + 'metadata_dir' => array( + 'type' => 'directory', + 'default' => PEAR_CONFIG_DEFAULT_METADATA_DIR, + 'doc' => 'directory where metadata files are installed (registry, filemap, channels, ...)', + 'prompt' => 'PEAR metadata directory', + 'group' => 'File Locations (Advanced)', + ), // Maintainers 'username' => array( 'type' => 'string', @@ -592,10 +624,10 @@ class PEAR_Config extends PEAR * * @see PEAR_Config::singleton */ - function PEAR_Config($user_file = '', $system_file = '', $ftp_file = false, + function __construct($user_file = '', $system_file = '', $ftp_file = false, $strict = true) { - $this->PEAR(); + parent::__construct(); PEAR_Installer_Role::initializeConfig($this); $sl = DIRECTORY_SEPARATOR; if (empty($user_file)) { @@ -647,7 +679,9 @@ function PEAR_Config($user_file = '', $system_file = '', $ftp_file = false, $this->configuration['default'][$key] = $info['default']; } - $this->_registry['default'] = &new PEAR_Registry($this->configuration['default']['php_dir']); + $this->_registry['default'] = new PEAR_Registry( + $this->configuration['default']['php_dir'], false, false, + $this->configuration['default']['metadata_dir']); $this->_registry['default']->setConfig($this, false); $this->_regInitialized['default'] = false; //$GLOBALS['_PEAR_Config_instance'] = &$this; @@ -655,9 +689,8 @@ function PEAR_Config($user_file = '', $system_file = '', $ftp_file = false, /** * Return the default locations of user and system configuration files - * @static */ - function getDefaultConfigFiles() + public static function getDefaultConfigFiles() { $sl = DIRECTORY_SEPARATOR; if (OS_WINDOWS) { @@ -684,17 +717,15 @@ function getDefaultConfigFiles() * * @return object an existing or new PEAR_Config instance * - * @access public - * * @see PEAR_Config::PEAR_Config */ - function &singleton($user_file = '', $system_file = '', $strict = true) + public static function &singleton($user_file = '', $system_file = '', $strict = true) { if (is_object($GLOBALS['_PEAR_Config_instance'])) { return $GLOBALS['_PEAR_Config_instance']; } - $t_conf = &new PEAR_Config($user_file, $system_file, false, $strict); + $t_conf = new PEAR_Config($user_file, $system_file, false, $strict); if ($t_conf->_errorsFound > 0) { return $t_conf->lastError; } @@ -754,7 +785,9 @@ function readConfigFile($file = null, $layer = 'user', $strict = true) $this->configuration[$layer] = $data; $this->_setupChannels(); if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { - $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer] = new PEAR_Registry( + $phpdir, false, false, + $this->get('metadata_dir', $layer, 'pear.php.net')); $this->_registry[$layer]->setConfig($this, false); $this->_regInitialized[$layer] = false; } else { @@ -783,7 +816,7 @@ function readFTPConfigFile($path) return PEAR::raiseError('PEAR_RemoteInstaller must be installed to use remote config'); } - $this->_ftp = &new PEAR_FTP; + $this->_ftp = new PEAR_FTP; $this->_ftp->pushErrorHandling(PEAR_ERROR_RETURN); $e = $this->_ftp->init($path); if (PEAR::isError($e)) { @@ -911,7 +944,9 @@ function mergeConfigFile($file, $override = true, $layer = 'user', $strict = tru $this->_setupChannels(); if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) { - $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer] = new PEAR_Registry( + $phpdir, false, false, + $this->get('metadata_dir', $layer, 'pear.php.net')); $this->_registry[$layer]->setConfig($this, false); $this->_regInitialized[$layer] = false; } else { @@ -924,9 +959,8 @@ function mergeConfigFile($file, $override = true, $layer = 'user', $strict = tru * @param array * @param array * @return array - * @static */ - function arrayMergeRecursive($arr2, $arr1) + public static function arrayMergeRecursive($arr2, $arr1) { $ret = array(); foreach ($arr2 as $key => $data) { @@ -1022,16 +1056,12 @@ function _readConfigDataFrom($file) } $size = filesize($file); - $rt = get_magic_quotes_runtime(); - set_magic_quotes_runtime(0); fclose($fp); $contents = file_get_contents($file); if (empty($contents)) { return $this->raiseError('Configuration file "' . $file . '" is empty'); } - set_magic_quotes_runtime($rt); - $version = false; if (preg_match('/^#PEAR_Config\s+(\S+)\s+/si', $contents, $matches)) { $version = $matches[1]; @@ -1357,7 +1387,7 @@ function get($key, $layer = null, $channel = false) if ($key == 'preferred_mirror') { $reg = &$this->getRegistry(); if (is_object($reg)) { - $chan = &$reg->getChannel($channel); + $chan = $reg->getChannel($channel); if (PEAR::isError($chan)) { return $channel; } @@ -1383,7 +1413,7 @@ function get($key, $layer = null, $channel = false) if ($key == 'preferred_mirror') { $reg = &$this->getRegistry(); if (is_object($reg)) { - $chan = &$reg->getChannel($channel); + $chan = $reg->getChannel($channel); if (PEAR::isError($chan)) { return $channel; } @@ -1434,7 +1464,7 @@ function _getChannelValue($key, $layer, $channel) if ($ret !== null) { $reg = &$this->getRegistry($layer); if (is_object($reg)) { - $chan = &$reg->getChannel($channel); + $chan = $reg->getChannel($channel); if (PEAR::isError($chan)) { return $channel; } @@ -1492,7 +1522,7 @@ function set($key, $value, $layer = 'user', $channel = false) $reg = &$this->getRegistry($layer); if (is_object($reg)) { - $chan = &$reg->getChannel($channel ? $channel : 'pear.php.net'); + $chan = $reg->getChannel($channel ? $channel : 'pear.php.net'); if (PEAR::isError($chan)) { return false; } @@ -1574,7 +1604,7 @@ function set($key, $value, $layer = 'user', $channel = false) if ($key == 'php_dir' && !$this->_noRegistry) { if (!isset($this->_registry[$layer]) || $value != $this->_registry[$layer]->install_dir) { - $this->_registry[$layer] = &new PEAR_Registry($value); + $this->_registry[$layer] = new PEAR_Registry($value); $this->_regInitialized[$layer] = false; $this->_registry[$layer]->setConfig($this, false); } @@ -1604,7 +1634,9 @@ function _lazyChannelSetup($uselayer = false) if (!is_object($this->_registry[$layer])) { if ($phpdir = $this->get('php_dir', $layer, 'pear.php.net')) { - $this->_registry[$layer] = &new PEAR_Registry($phpdir); + $this->_registry[$layer] = new PEAR_Registry( + $phpdir, false, false, + $this->get('metadata_dir', $layer, 'pear.php.net')); $this->_registry[$layer]->setConfig($this, false); $this->_regInitialized[$layer] = false; } else { @@ -2035,7 +2067,7 @@ function &getREST($version, $options = array()) require_once 'PEAR/REST/' . $version . '.php'; } - $remote = &new $class($this, $options); + $remote = new $class($this, $options); return $remote; } @@ -2088,7 +2120,9 @@ function setInstallRoot($root) continue; } $this->_registry[$layer] = - &new PEAR_Registry($this->get('php_dir', $layer, 'pear.php.net')); + new PEAR_Registry( + $this->get('php_dir', $layer, 'pear.php.net'), false, false, + $this->get('metadata_dir', $layer, 'pear.php.net')); $this->_registry[$layer]->setConfig($this, false); $this->_regInitialized[$layer] = false; } diff --git a/WEB-INF/lib/pear/PEAR/Dependency2.php b/WEB-INF/lib/pear/PEAR/Dependency2.php index f3ddeb1cf..635c551ec 100644 --- a/WEB-INF/lib/pear/PEAR/Dependency2.php +++ b/WEB-INF/lib/pear/PEAR/Dependency2.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Dependency2.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -31,7 +30,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -82,7 +81,7 @@ class PEAR_Dependency2 * @param array format of PEAR_Registry::parsedPackageName() * @param int installation state (one of PEAR_VALIDATE_*) */ - function PEAR_Dependency2(&$config, $installoptions, $package, + function __construct(&$config, $installoptions, $package, $state = PEAR_VALIDATE_INSTALLING) { $this->_config = &$config; @@ -541,7 +540,7 @@ function validatePhpDependency($dep) */ function getPEARVersion() { - return '1.9.4'; + return '1.10.1'; } function validatePearinstallerDependency($dep) @@ -607,7 +606,7 @@ function validateSubpackageDependency($dep, $required, $params) * @param boolean whether this is a required dependency * @param array a list of downloaded packages to be installed, if any * @param boolean if true, then deps on pear.php.net that fail will also check - * against pecl.php.net packages to accomodate extensions that have + * against pecl.php.net packages to accommodate extensions that have * moved to pecl.php.net from pear.php.net */ function validatePackageDependency($dep, $required, $params, $depv1 = false) @@ -894,9 +893,9 @@ function validatePackageUninstall(&$dl) if (!class_exists('PEAR_Downloader_Package')) { require_once 'PEAR/Downloader/Package.php'; } - $dp = &new PEAR_Downloader_Package($dl); + $dp = new PEAR_Downloader_Package($dl); $dp->setPackageFile($downloaded[$i]); - $params[$i] = &$dp; + $params[$i] = $dp; } // check cache @@ -1175,7 +1174,7 @@ function validatePackage($pkg, &$dl, $params = array()) require_once 'PEAR/Downloader/Package.php'; } - $dp = &new PEAR_Downloader_Package($dl); + $dp = new PEAR_Downloader_Package($dl); if (is_object($pkg)) { $dp->setPackageFile($pkg); } else { @@ -1199,7 +1198,7 @@ function validatePackage($pkg, &$dl, $params = array()) } foreach ($ds as $d) { - $checker = &new PEAR_Dependency2($this->_config, $this->_options, + $checker = new PEAR_Dependency2($this->_config, $this->_options, array('channel' => $channel, 'package' => $package), $this->_state); $dep = $d['dep']; $required = $d['type'] == 'required'; @@ -1355,4 +1354,4 @@ function warning($msg) return array(sprintf($msg, $this->_registry->parsedPackageNameToString( $this->_currentPackage, true))); } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/DependencyDB.php b/WEB-INF/lib/pear/PEAR/DependencyDB.php index 948f0c9d7..1ee604343 100644 --- a/WEB-INF/lib/pear/PEAR/DependencyDB.php +++ b/WEB-INF/lib/pear/PEAR/DependencyDB.php @@ -10,7 +10,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: DependencyDB.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -30,7 +29,7 @@ * @author Tomas V.V.Cox * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -89,9 +88,8 @@ class PEAR_DependencyDB * @param PEAR_Config * @param string|false full path to the dependency database, or false to use default * @return PEAR_DependencyDB|PEAR_Error - * @static */ - function &singleton(&$config, $depdb = false) + public static function &singleton(&$config, $depdb = false) { $phpdir = $config->get('php_dir', null, 'pear.php.net'); if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir])) { @@ -122,8 +120,11 @@ function setConfig(&$config, $depdb = false) $this->_registry = &$this->_config->getRegistry(); if (!$depdb) { - $this->_depdb = $this->_config->get('php_dir', null, 'pear.php.net') . - DIRECTORY_SEPARATOR . '.depdb'; + $dir = $this->_config->get('metadata_dir', null, 'pear.php.net'); + if (!$dir) { + $dir = $this->_config->get('php_dir', null, 'pear.php.net'); + } + $this->_depdb = $dir . DIRECTORY_SEPARATOR . '.depdb'; } else { $this->_depdb = $depdb; } @@ -550,12 +551,9 @@ function _getDepDB() return $err; } - $rt = get_magic_quotes_runtime(); - set_magic_quotes_runtime(0); clearstatcache(); fclose($fp); $data = unserialize(file_get_contents($this->_depdb)); - set_magic_quotes_runtime($rt); $this->_cache = $data; return $data; } @@ -577,10 +575,7 @@ function _writeDepDB(&$deps) return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing"); } - $rt = get_magic_quotes_runtime(); - set_magic_quotes_runtime(0); fwrite($fp, serialize($deps)); - set_magic_quotes_runtime($rt); fclose($fp); $this->_unlock(); $this->_cache = $deps; @@ -766,4 +761,4 @@ function _registerDep(&$data, &$pkg, $dep, $type, $group = false) ); } } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Downloader.php b/WEB-INF/lib/pear/PEAR/Downloader.php index 730df0b73..6d6cdd7a2 100644 --- a/WEB-INF/lib/pear/PEAR/Downloader.php +++ b/WEB-INF/lib/pear/PEAR/Downloader.php @@ -12,7 +12,6 @@ * @author Martin Jansen * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Downloader.php 313024 2011-07-06 19:51:24Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.3.0 */ @@ -39,7 +38,7 @@ * @author Martin Jansen * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.3.0 */ @@ -140,27 +139,44 @@ class PEAR_Downloader extends PEAR_Common */ var $_downloadDir; + /** + * List of methods that can be called both statically and non-statically. + * @var array + */ + protected static $bivalentMethods = array( + 'setErrorHandling' => true, + 'raiseError' => true, + 'throwError' => true, + 'pushErrorHandling' => true, + 'popErrorHandling' => true, + 'downloadHttp' => true, + ); + /** * @param PEAR_Frontend_* * @param array * @param PEAR_Config */ - function PEAR_Downloader(&$ui, $options, &$config) + function __construct($ui = null, $options = array(), $config = null) { - parent::PEAR_Common(); + parent::__construct(); $this->_options = $options; - $this->config = &$config; - $this->_preferredState = $this->config->get('preferred_state'); + if ($config !== null) { + $this->config = &$config; + $this->_preferredState = $this->config->get('preferred_state'); + } $this->ui = &$ui; if (!$this->_preferredState) { // don't inadvertantly use a non-set preferred_state $this->_preferredState = null; } - if (isset($this->_options['installroot'])) { - $this->config->setInstallRoot($this->_options['installroot']); + if ($config !== null) { + if (isset($this->_options['installroot'])) { + $this->config->setInstallRoot($this->_options['installroot']); + } + $this->_registry = &$config->getRegistry(); } - $this->_registry = &$config->getRegistry(); if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) { $this->_installed = $this->_registry->listAllPackages(); @@ -235,12 +251,12 @@ function discover($channel) * @param PEAR_Downloader * @return PEAR_Downloader_Package */ - function &newDownloaderPackage(&$t) + function newDownloaderPackage(&$t) { if (!class_exists('PEAR_Downloader_Package')) { require_once 'PEAR/Downloader/Package.php'; } - $a = &new PEAR_Downloader_Package($t); + $a = new PEAR_Downloader_Package($t); return $a; } @@ -256,7 +272,7 @@ function &getDependency2Object(&$c, $i, $p, $s) if (!class_exists('PEAR_Dependency2')) { require_once 'PEAR/Dependency2.php'; } - $z = &new PEAR_Dependency2($c, $i, $p, $s); + $z = new PEAR_Dependency2($c, $i, $p, $s); return $z; } @@ -274,7 +290,7 @@ function &download($params) $channelschecked = array(); // convert all parameters into PEAR_Downloader_Package objects foreach ($params as $i => $param) { - $params[$i] = &$this->newDownloaderPackage($this); + $params[$i] = $this->newDownloaderPackage($this); PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); $err = $params[$i]->initialize($param); PEAR::staticPopErrorHandling(); @@ -315,7 +331,7 @@ function &download($params) require_once 'System.php'; } - $curchannel = &$this->_registry->getChannel($params[$i]->getChannel()); + $curchannel = $this->_registry->getChannel($params[$i]->getChannel()); if (PEAR::isError($curchannel)) { PEAR::staticPopErrorHandling(); return $this->raiseError($curchannel); @@ -331,7 +347,10 @@ function &download($params) $a = $this->downloadHttp($url, $this->ui, $dir, null, $curchannel->lastModified()); PEAR::staticPopErrorHandling(); - if (PEAR::isError($a) || !$a) { + if ($a === false) { + //channel.xml not modified + break; + } else if (PEAR::isError($a)) { // Attempt fallback to https automatically PEAR::pushErrorHandling(PEAR_ERROR_RETURN); $a = $this->downloadHttp('https://' . $mirror . @@ -806,7 +825,7 @@ function _getPackageDownloadUrl($parr) } while (false); } - $chan = &$this->_registry->getChannel($parr['channel']); + $chan = $this->_registry->getChannel($parr['channel']); if (PEAR::isError($chan)) { return $chan; } @@ -929,7 +948,7 @@ function _getDepPackageDownloadUrl($dep, $parr) $curchannel = $this->config->get('default_channel'); if (isset($dep['uri'])) { $xsdversion = '2.0'; - $chan = &$this->_registry->getChannel('__uri'); + $chan = $this->_registry->getChannel('__uri'); if (PEAR::isError($chan)) { return $chan; } @@ -954,7 +973,7 @@ function _getDepPackageDownloadUrl($dep, $parr) } while (false); } - $chan = &$this->_registry->getChannel($remotechannel); + $chan = $this->_registry->getChannel($remotechannel); if (PEAR::isError($chan)) { return $chan; } @@ -969,7 +988,7 @@ function _getDepPackageDownloadUrl($dep, $parr) } if (isset($dep['uri'])) { - $info = &$this->newDownloaderPackage($this); + $info = $this->newDownloaderPackage($this); PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); $err = $info->initialize($dep); PEAR::staticPopErrorHandling(); @@ -1523,18 +1542,21 @@ function _sortInstall($a, $b) * use false to return the header values from this download * @param false|array $accept Accept headers to send * @param false|string $channel Channel to use for retrieving authentication - * @return string|array Returns the full path of the downloaded file or a PEAR - * error on failure. If the error is caused by - * socket-related errors, the error object will - * have the fsockopen error code available through - * getCode(). If caching is requested, then return the header - * values. + * @return mixed Returns the full path of the downloaded file or a PEAR + * error on failure. If the error is caused by + * socket-related errors, the error object will + * have the fsockopen error code available through + * getCode(). If caching is requested, then return the header + * values. + * If $lastmodified was given and the there are no changes, + * boolean false is returned. * * @access public */ - function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null, - $accept = false, $channel = false) - { + public static function _downloadHttp( + $object, $url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null, + $accept = false, $channel = false + ) { static $redirect = 0; // always reset , so we are clean case of error $wasredirect = $redirect; @@ -1556,8 +1578,8 @@ function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodifi $port = isset($info['port']) ? $info['port'] : null; $path = isset($info['path']) ? $info['path'] : null; - if (isset($this)) { - $config = &$this->config; + if ($object !== null) { + $config = $object->config; } else { $config = &PEAR_Config::singleton(); } @@ -1639,9 +1661,9 @@ function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodifi } $request .= $ifmodifiedsince . - "User-Agent: PEAR/1.9.4/PHP/" . PHP_VERSION . "\r\n"; + "User-Agent: PEAR/1.10.1/PHP/" . PHP_VERSION . "\r\n"; - if (isset($this)) { // only pass in authentication for non-static calls + if ($object !== null) { // only pass in authentication for non-static calls $username = $config->get('username', null, $channel); $password = $config->get('password', null, $channel); if ($username && $password) { @@ -1689,7 +1711,7 @@ function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodifi } $redirect = $wasredirect + 1; - return $this->downloadHttp($headers['location'], + return static::_downloadHttp($object, $headers['location'], $ui, $save_dir, $callback, $lastmodified, $accept); } @@ -1763,4 +1785,4 @@ function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodifi } return $dest_file; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Downloader/Package.php b/WEB-INF/lib/pear/PEAR/Downloader/Package.php index 987c96567..fe979eb67 100644 --- a/WEB-INF/lib/pear/PEAR/Downloader/Package.php +++ b/WEB-INF/lib/pear/PEAR/Downloader/Package.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Package.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -50,7 +49,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -130,7 +129,7 @@ class PEAR_Downloader_Package /** * @param PEAR_Downloader */ - function PEAR_Downloader_Package(&$downloader) + function __construct(&$downloader) { $this->_downloader = &$downloader; $this->_config = &$this->_downloader->config; @@ -397,9 +396,8 @@ function alreadyValidated() /** * Remove packages to be downloaded that are already installed * @param array of PEAR_Downloader_Package objects - * @static */ - function removeInstalled(&$params) + public static function removeInstalled(&$params) { if (!isset($params[0])) { return; @@ -415,7 +413,7 @@ function removeInstalled(&$params) if ($param->_installRegistry->packageExists($package, $channel)) { $packageVersion = $param->_installRegistry->packageInfo($package, 'version', $channel); if (version_compare($packageVersion, $param->getVersion(), '==')) { - if (!isset($options['force'])) { + if (!isset($options['force']) && !isset($options['packagingroot'])) { $info = $param->getParsedPackage(); unset($info['version']); unset($info['state']); @@ -427,7 +425,7 @@ function removeInstalled(&$params) $params[$i] = false; } } elseif (!isset($options['force']) && !isset($options['upgrade']) && - !isset($options['soft'])) { + !isset($options['soft']) && !isset($options['packagingroot'])) { $info = $param->getParsedPackage(); $param->_downloader->log(1, 'Skipping package "' . $param->getShortName() . @@ -1243,7 +1241,7 @@ function isInstalled($dep, $oper = '==') * @param array $errorparams empty array * @return array array of stupid duplicated packages in PEAR_Downloader_Package obejcts */ - function detectStupidDuplicates($params, &$errorparams) + public static function detectStupidDuplicates($params, &$errorparams) { $existing = array(); foreach ($params as $i => $param) { @@ -1281,9 +1279,8 @@ function detectStupidDuplicates($params, &$errorparams) /** * @param array * @param bool ignore install groups - for final removal of dupe packages - * @static */ - function removeDuplicates(&$params, $ignoreGroups = false) + public static function removeDuplicates(&$params, $ignoreGroups = false) { $pnames = array(); foreach ($params as $i => $param) { @@ -1345,9 +1342,8 @@ function setExplicitState($s) } /** - * @static */ - function mergeDependencies(&$params) + public static function mergeDependencies(&$params) { $bundles = $newparams = array(); foreach ($params as $i => $param) { @@ -1384,14 +1380,14 @@ function mergeDependencies(&$params) $obj->setExplicitState($s); } - $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + $obj = new PEAR_Downloader_Package($params[$i]->getDownloader()); PEAR::pushErrorHandling(PEAR_ERROR_RETURN); if (PEAR::isError($dir = $dl->getDownloadDir())) { PEAR::popErrorHandling(); return $dir; } - - $e = $obj->_fromFile($a = $dir . DIRECTORY_SEPARATOR . $file); + $a = $dir . DIRECTORY_SEPARATOR . $file; + $e = $obj->_fromFile($a); PEAR::popErrorHandling(); if (PEAR::isError($e)) { if (!isset($options['soft'])) { @@ -1400,10 +1396,9 @@ function mergeDependencies(&$params) continue; } - $j = &$obj; - if (!PEAR_Downloader_Package::willDownload($j, - array_merge($params, $newparams)) && !$param->isInstalled($j)) { - $newparams[] = &$j; + if (!PEAR_Downloader_Package::willDownload($obj, + array_merge($params, $newparams)) && !$param->isInstalled($obj)) { + $newparams[] = $obj; } } } @@ -1437,7 +1432,7 @@ function mergeDependencies(&$params) // convert the dependencies into PEAR_Downloader_Package objects for the next time around $params[$i]->_downloadDeps = array(); foreach ($newdeps as $dep) { - $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader()); + $obj = new PEAR_Downloader_Package($params[$i]->getDownloader()); if ($s = $params[$i]->explicitState()) { $obj->setExplicitState($s); } @@ -1459,8 +1454,7 @@ function mergeDependencies(&$params) } } - $j = &$obj; - $newparams[] = &$j; + $newparams[] = $obj; } } @@ -1476,9 +1470,8 @@ function mergeDependencies(&$params) /** - * @static */ - function willDownload($param, $params) + public static function willDownload($param, $params) { if (!is_array($params)) { return false; @@ -1501,7 +1494,7 @@ function willDownload($param, $params) */ function &getPackagefileObject(&$c, $d) { - $a = &new PEAR_PackageFile($c, $d); + $a = new PEAR_PackageFile($c, $d); return $a; } @@ -1576,7 +1569,7 @@ function _fromUrl($param, $saveparam = '') if ($this->_rawpackagefile) { require_once 'Archive/Tar.php'; - $tar = &new Archive_Tar($file); + $tar = new Archive_Tar($file); $packagexml = $tar->extractInString('package2.xml'); if (!$packagexml) { $packagexml = $tar->extractInString('package.xml'); @@ -1985,4 +1978,4 @@ function _analyzeDownloadURL($info, $param, $pname, $params = null, $optional = return $info; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/ErrorStack.php b/WEB-INF/lib/pear/PEAR/ErrorStack.php index 0303f5273..7b705bdbb 100644 --- a/WEB-INF/lib/pear/PEAR/ErrorStack.php +++ b/WEB-INF/lib/pear/PEAR/ErrorStack.php @@ -23,7 +23,6 @@ * @author Greg Beaver * @copyright 2004-2008 Greg Beaver * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: ErrorStack.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR_ErrorStack */ @@ -132,12 +131,11 @@ * $local_stack = new PEAR_ErrorStack('MyPackage'); * * @author Greg Beaver - * @version 1.9.4 + * @version 1.10.1 * @package PEAR_ErrorStack * @category Debugging * @copyright 2004-2008 Greg Beaver * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: ErrorStack.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR_ErrorStack */ class PEAR_ErrorStack { @@ -194,43 +192,43 @@ class PEAR_ErrorStack { * @access protected */ var $_contextCallback = false; - + /** * If set to a valid callback, this will be called every time an error * is pushed onto the stack. The return value will be used to determine * whether to allow an error to be pushed or logged. - * + * * The return value must be one an PEAR_ERRORSTACK_* constant * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG * @var false|string|array * @access protected */ var $_errorCallback = array(); - + /** * PEAR::Log object for logging errors * @var false|Log * @access protected */ var $_logger = false; - + /** * Error messages - designed to be overridden * @var array * @abstract */ var $_errorMsgs = array(); - + /** * Set up a new error stack - * + * * @param string $package name of the package this error stack represents * @param callback $msgCallback callback used for error message generation * @param callback $contextCallback callback used for context generation, * defaults to {@link getFileLine()} * @param boolean $throwPEAR_Error */ - function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false, + function __construct($package, $msgCallback = false, $contextCallback = false, $throwPEAR_Error = false) { $this->_package = $package; @@ -250,12 +248,13 @@ function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = fals * defaults to {@link getFileLine()} * @param boolean $throwPEAR_Error * @param string $stackClass class to instantiate - * @static + * * @return PEAR_ErrorStack */ - function &singleton($package, $msgCallback = false, $contextCallback = false, - $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack') - { + public static function &singleton( + $package, $msgCallback = false, $contextCallback = false, + $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack' + ) { if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]; } @@ -297,15 +296,14 @@ function _handleError($err) /** * Set up a PEAR::Log object for all error stacks that don't have one * @param Log $log - * @static */ - function setDefaultLogger(&$log) + public static function setDefaultLogger(&$log) { if (is_object($log) && method_exists($log, 'log') ) { $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log; } elseif (is_callable($log)) { $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log; - } + } } /** @@ -358,9 +356,8 @@ function getMessageCallback() * messages for a singleton * @param array|string Callback function/method * @param string Package name, or false for all packages - * @static */ - function setDefaultCallback($callback = false, $package = false) + public static function setDefaultCallback($callback = false, $package = false) { if (!is_callable($callback)) { $callback = false; @@ -432,9 +429,8 @@ function popCallback() * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG * @see staticPopCallback(), pushCallback() * @param string|array $cb - * @static */ - function staticPushCallback($cb) + public static function staticPushCallback($cb) { array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb); } @@ -443,9 +439,8 @@ function staticPushCallback($cb) * Remove a temporary overriding error callback * @see staticPushCallback() * @return array|string|false - * @static */ - function staticPopCallback() + public static function staticPopCallback() { $ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']); if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) { @@ -604,11 +599,11 @@ function push($code, $level = 'error', $params = array(), $msg = false, * to find error context * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also * thrown. see docs for {@link push()} - * @static */ - function staticPush($package, $code, $level = 'error', $params = array(), - $msg = false, $repackage = false, $backtrace = false) - { + public static function staticPush( + $package, $code, $level = 'error', $params = array(), + $msg = false, $repackage = false, $backtrace = false + ) { $s = &PEAR_ErrorStack::singleton($package); if ($s->_contextCallback) { if (!$backtrace) { @@ -750,9 +745,8 @@ function getErrors($purge = false, $level = false) * @param string|false Package name to check for errors * @param string Level name to check for a particular severity * @return boolean - * @static */ - function staticHasErrors($package = false, $level = false) + public static function staticHasErrors($package = false, $level = false) { if ($package) { if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) { @@ -776,12 +770,13 @@ function staticHasErrors($package = false, $level = false) * @param boolean $merge Set to return a flat array, not organized by package * @param array $sortfunc Function used to sort a merged array - default * sorts by time, and should be good for most cases - * @static + * * @return array */ - function staticGetErrors($purge = false, $level = false, $merge = false, - $sortfunc = array('PEAR_ErrorStack', '_sortErrors')) - { + public static function staticGetErrors( + $purge = false, $level = false, $merge = false, + $sortfunc = array('PEAR_ErrorStack', '_sortErrors') + ) { $ret = array(); if (!is_callable($sortfunc)) { $sortfunc = array('PEAR_ErrorStack', '_sortErrors'); @@ -806,7 +801,7 @@ function staticGetErrors($purge = false, $level = false, $merge = false, * Error sorting function, sorts by time * @access private */ - function _sortErrors($a, $b) + public static function _sortErrors($a, $b) { if ($a['time'] == $b['time']) { return 0; @@ -829,9 +824,8 @@ function _sortErrors($a, $b) * @param unused * @param integer backtrace frame. * @param array Results of debug_backtrace() - * @static */ - function getFileLine($code, $params, $backtrace = null) + public static function getFileLine($code, $params, $backtrace = null) { if ($backtrace === null) { return false; @@ -903,10 +897,10 @@ function getFileLine($code, $params, $backtrace = null) * @param PEAR_ErrorStack * @param array * @param string|false Pre-generated error message template - * @static + * * @return string */ - function getErrorMessage(&$stack, $err, $template = false) + public static function getErrorMessage(&$stack, $err, $template = false) { if ($template) { $mainmsg = $template; diff --git a/WEB-INF/lib/pear/PEAR/Exception.php b/WEB-INF/lib/pear/PEAR/Exception.php index 4a0e7b86f..0aba17104 100644 --- a/WEB-INF/lib/pear/PEAR/Exception.php +++ b/WEB-INF/lib/pear/PEAR/Exception.php @@ -13,7 +13,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Exception.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.3.3 */ @@ -89,7 +88,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.3.3 * diff --git a/WEB-INF/lib/pear/PEAR/FixPHP5PEARWarnings.php b/WEB-INF/lib/pear/PEAR/FixPHP5PEARWarnings.php deleted file mode 100644 index be5dc3ce7..000000000 --- a/WEB-INF/lib/pear/PEAR/FixPHP5PEARWarnings.php +++ /dev/null @@ -1,7 +0,0 @@ - \ No newline at end of file diff --git a/WEB-INF/lib/pear/PEAR/Frontend.php b/WEB-INF/lib/pear/PEAR/Frontend.php index 531e541f9..8c8c8c6b4 100644 --- a/WEB-INF/lib/pear/PEAR/Frontend.php +++ b/WEB-INF/lib/pear/PEAR/Frontend.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Frontend.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -39,7 +38,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -48,9 +47,8 @@ class PEAR_Frontend extends PEAR /** * Retrieve the frontend object * @return PEAR_Frontend_CLI|PEAR_Frontend_Web|PEAR_Frontend_Gtk - * @static */ - function &singleton($type = null) + public static function &singleton($type = null) { if ($type === null) { if (!isset($GLOBALS['_PEAR_FRONTEND_SINGLETON'])) { @@ -71,9 +69,8 @@ function &singleton($type = null) * _ => DIRECTORY_SEPARATOR (PEAR_Frontend_CLI is in PEAR/Frontend/CLI.php) * @param string $uiclass full class name * @return PEAR_Frontend - * @static */ - function &setFrontendClass($uiclass) + public static function &setFrontendClass($uiclass) { if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], $uiclass)) { @@ -88,7 +85,7 @@ function &setFrontendClass($uiclass) } if (class_exists($uiclass)) { - $obj = &new $uiclass; + $obj = new $uiclass; // quick test to see if this class implements a few of the most // important frontend methods if (is_a($obj, 'PEAR_Frontend')) { @@ -111,9 +108,8 @@ function &setFrontendClass($uiclass) * Frontends are expected to be a descendant of PEAR_Frontend * @param PEAR_Frontend * @return PEAR_Frontend - * @static */ - function &setFrontendObject($uiobject) + public static function &setFrontendObject($uiobject) { if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) && is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], get_class($uiobject))) { @@ -134,9 +130,8 @@ function &setFrontendObject($uiobject) /** * @param string $path relative or absolute include path * @return boolean - * @static */ - function isIncludeable($path) + public static function isIncludeable($path) { if (file_exists($path) && is_readable($path)) { return true; diff --git a/WEB-INF/lib/pear/PEAR/Frontend/CLI.php b/WEB-INF/lib/pear/PEAR/Frontend/CLI.php index 340b99b79..f0723d039 100644 --- a/WEB-INF/lib/pear/PEAR/Frontend/CLI.php +++ b/WEB-INF/lib/pear/PEAR/Frontend/CLI.php @@ -10,7 +10,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: CLI.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -27,7 +26,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -47,9 +46,9 @@ class PEAR_Frontend_CLI extends PEAR_Frontend 'normal' => '', ); - function PEAR_Frontend_CLI() + function __construct() { - parent::PEAR(); + parent::__construct(); $term = getenv('TERM'); //(cox) $_ENV is empty for me in 4.1.1 if (function_exists('posix_isatty') && !posix_isatty(1)) { // output is being redirected to a file or through a pipe @@ -748,4 +747,4 @@ function _display($text) { print $text; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Installer.php b/WEB-INF/lib/pear/PEAR/Installer.php index eb17ca791..d5cc7df69 100644 --- a/WEB-INF/lib/pear/PEAR/Installer.php +++ b/WEB-INF/lib/pear/PEAR/Installer.php @@ -12,7 +12,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Installer.php 313024 2011-07-06 19:51:24Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -36,7 +35,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -116,9 +115,9 @@ class PEAR_Installer extends PEAR_Downloader * * @access public */ - function PEAR_Installer(&$ui) + function __construct(&$ui) { - parent::PEAR_Common(); + parent::__construct($ui, array(), null); $this->setFrontendObject($ui); $this->debug = $this->config->get('verbose'); } @@ -589,7 +588,7 @@ function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options) foreach ($atts as $tag => $raw) { $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag); $task = "PEAR_Task_$tag"; - $task = &new $task($this->config, $this, PEAR_TASK_INSTALL); + $task = new $task($this->config, $this, PEAR_TASK_INSTALL); if (!$task->isScript()) { // scripts are only handled after installation $task->init($raw, $attribs, $pkg->getLastInstalledVersion()); $res = $task->startSession($pkg, $contents, $final_dest_file); @@ -808,7 +807,10 @@ function commitFileTransaction() if (!empty($res)) { $new = $this->_registry->getPackage($result[1], $result[0]); $this->file_operations[$key] = false; - $this->log(3, "file $data[0] was scheduled for removal from {$this->pkginfo->getName()} but is owned by {$new->getChannel()}/{$new->getName()}, removal has been cancelled."); + $pkginfoName = $this->pkginfo->getName(); + $newChannel = $new->getChannel(); + $newPackage = $new->getName(); + $this->log(3, "file $data[0] was scheduled for removal from $pkginfoName but is owned by $newChannel/$newPackage, removal has been cancelled."); } } } @@ -1017,42 +1019,6 @@ function mkDirHier($dir) return parent::mkDirHier($dir); } - // }}} - // {{{ download() - - /** - * Download any files and their dependencies, if necessary - * - * @param array a mixed list of package names, local files, or package.xml - * @param PEAR_Config - * @param array options from the command line - * @param array this is the array that will be populated with packages to - * install. Format of each entry: - * - * - * array('pkg' => 'package_name', 'file' => '/path/to/local/file', - * 'info' => array() // parsed package.xml - * ); - * - * @param array this will be populated with any error messages - * @param false private recursion variable - * @param false private recursion variable - * @param false private recursion variable - * @deprecated in favor of PEAR_Downloader - */ - function download($packages, $options, &$config, &$installpackages, - &$errors, $installed = false, $willinstall = false, $state = false) - { - // trickiness: initialize here - parent::PEAR_Downloader($this->ui, $options, $config); - $ret = parent::download($packages); - $errors = $this->getErrorMsgs(); - $installpackages = $this->getDownloadedPackages(); - trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " . - "in favor of PEAR_Downloader class", E_USER_WARNING); - return $ret; - } - // }}} // {{{ _parsePackageXml() @@ -1162,15 +1128,6 @@ function install($pkgfile, $options = array()) $pkgname = $pkg->getName(); $channel = $pkg->getChannel(); - if (isset($this->_options['packagingroot'])) { - $regdir = $this->_prependPath( - $this->config->get('php_dir', null, 'pear.php.net'), - $this->_options['packagingroot']); - - $packrootphp_dir = $this->_prependPath( - $this->config->get('php_dir', null, $channel), - $this->_options['packagingroot']); - } if (isset($options['installroot'])) { $this->config->setInstallRoot($options['installroot']); @@ -1182,7 +1139,21 @@ function install($pkgfile, $options = array()) $this->config->setInstallRoot(false); $this->_registry = &$this->config->getRegistry(); if (isset($this->_options['packagingroot'])) { - $installregistry = &new PEAR_Registry($regdir); + $regdir = $this->_prependPath( + $this->config->get('php_dir', null, 'pear.php.net'), + $this->_options['packagingroot']); + + $metadata_dir = $this->config->get('metadata_dir', null, 'pear.php.net'); + if ($metadata_dir) { + $metadata_dir = $this->_prependPath( + $metadata_dir, + $this->_options['packagingroot']); + } + $packrootphp_dir = $this->_prependPath( + $this->config->get('php_dir', null, $channel), + $this->_options['packagingroot']); + + $installregistry = new PEAR_Registry($regdir, false, false, $metadata_dir); if (!$installregistry->channelExists($channel, true)) { // we need to fake a channel-discover of this channel $chanobj = $this->_registry->getChannel($channel, true); @@ -1271,7 +1242,7 @@ function install($pkgfile, $options = array()) } } - $pfk = &new PEAR_PackageFile($this->config); + $pfk = new PEAR_PackageFile($this->config); $parentpkg = &$pfk->fromArray($parentreg); $installregistry->updatePackage2($parentpkg); } @@ -1535,7 +1506,7 @@ function _compileSourceFiles($savechannel, &$filelist) { require_once 'PEAR/Builder.php'; $this->log(1, "$this->source_files source files, building"); - $bob = &new PEAR_Builder($this->ui); + $bob = new PEAR_Builder($this->ui); $bob->debug = $this->debug; $built = $bob->build($filelist, array(&$this, '_buildCallback')); if (PEAR::isError($built)) { @@ -1679,7 +1650,7 @@ function uninstall($package, $options = array()) require_once 'PEAR/Dependency2.php'; } - $depchecker = &new PEAR_Dependency2($this->config, $options, + $depchecker = new PEAR_Dependency2($this->config, $options, array('channel' => $channel, 'package' => $package), PEAR_VALIDATE_UNINSTALLING); $e = $depchecker->validatePackageUninstall($this); @@ -1820,4 +1791,4 @@ function _buildCallback($what, $data) } // }}} -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role.php b/WEB-INF/lib/pear/PEAR/Installer/Role.php index 0c50fa79c..0623424a2 100644 --- a/WEB-INF/lib/pear/PEAR/Installer/Role.php +++ b/WEB-INF/lib/pear/PEAR/Installer/Role.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Role.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -25,7 +24,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -36,10 +35,8 @@ class PEAR_Installer_Role * * Never call this directly, it is called by the PEAR_Config constructor * @param PEAR_Config - * @access private - * @static */ - function initializeConfig(&$config) + public static function initializeConfig(&$config) { if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { PEAR_Installer_Role::registerRoles(); @@ -59,9 +56,8 @@ function initializeConfig(&$config) * @param string role name * @param PEAR_Config * @return PEAR_Installer_Role_Common - * @static */ - function &factory($pkg, $role, &$config) + public static function &factory($pkg, $role, &$config) { if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { PEAR_Installer_Role::registerRoles(); @@ -88,9 +84,8 @@ function &factory($pkg, $role, &$config) * @param string * @param bool clear cache * @return array - * @static */ - function getValidRoles($release, $clear = false) + public static function getValidRoles($release, $clear = false) { if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { PEAR_Installer_Role::registerRoles(); @@ -123,9 +118,8 @@ function getValidRoles($release, $clear = false) * roles are actually fully bundled releases of a package * @param bool clear cache * @return array - * @static */ - function getInstallableRoles($clear = false) + public static function getInstallableRoles($clear = false) { if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { PEAR_Installer_Role::registerRoles(); @@ -158,9 +152,8 @@ function getInstallableRoles($clear = false) * so a tests file tests/file.phpt is installed into PackageName/tests/filepath.php * @param bool clear cache * @return array - * @static */ - function getBaseinstallRoles($clear = false) + public static function getBaseinstallRoles($clear = false) { if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { PEAR_Installer_Role::registerRoles(); @@ -190,9 +183,8 @@ function getBaseinstallRoles($clear = false) * like the "php" role. * @param bool clear cache * @return array - * @static */ - function getPhpRoles($clear = false) + public static function getPhpRoles($clear = false) { if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) { PEAR_Installer_Role::registerRoles(); @@ -226,10 +218,8 @@ function getPhpRoles($clear = false) * included. * * @return bool TRUE on success, a PEAR error on failure - * @access public - * @static */ - function registerRoles($dir = null) + public static function registerRoles($dir = null) { $GLOBALS['_PEAR_INSTALLER_ROLES'] = array(); $parser = new PEAR_XMLParser; diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Cfg.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Cfg.php index 762012248..903b1d641 100644 --- a/WEB-INF/lib/pear/PEAR/Installer/Role/Cfg.php +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Cfg.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 2007-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Cfg.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.7.0 */ @@ -20,7 +19,7 @@ * @author Greg Beaver * @copyright 2007-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.7.0 */ diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Common.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Common.php index 23e7348d7..df0b3c66c 100644 --- a/WEB-INF/lib/pear/PEAR/Installer/Role/Common.php +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Common.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2006 The PHP Group * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -24,7 +23,7 @@ * @author Greg Beaver * @copyright 1997-2006 The PHP Group * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -39,7 +38,7 @@ class PEAR_Installer_Role_Common /** * @param PEAR_Config */ - function PEAR_Installer_Role_Common(&$config) + function __construct(&$config) { $this->config = $config; } @@ -171,4 +170,4 @@ function isExtension() return $roleInfo['phpextension']; } } -?> \ No newline at end of file +?> diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Data.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Data.php index e3b7fa2ff..1a2c9c30c 100644 --- a/WEB-INF/lib/pear/PEAR/Installer/Role/Data.php +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Data.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Data.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -20,7 +19,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Doc.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Doc.php index d592ffff0..675cc8777 100644 --- a/WEB-INF/lib/pear/PEAR/Installer/Role/Doc.php +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Doc.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Doc.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -20,7 +19,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Ext.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Ext.php index eceb0279f..6224e2b89 100644 --- a/WEB-INF/lib/pear/PEAR/Installer/Role/Ext.php +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Ext.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Ext.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -20,7 +19,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Man.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Man.php new file mode 100644 index 000000000..5c3a842b8 --- /dev/null +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Man.php @@ -0,0 +1,28 @@ + + * @copyright 2011 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.10.0 + */ + +/** + * @category pear + * @package PEAR + * @author Hannes Magnusson + * @copyright 2011 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.10.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.10.0 + */ +class PEAR_Installer_Role_Man extends PEAR_Installer_Role_Common {} +?> diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Man.xml b/WEB-INF/lib/pear/PEAR/Installer/Role/Man.xml new file mode 100644 index 000000000..ec2bd76c3 --- /dev/null +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Man.xml @@ -0,0 +1,15 @@ + + php + extsrc + extbin + zendextsrc + zendextbin + 1 + man_dir + 1 + + + + + + diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Php.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Php.php index e2abf44ee..d1b97a863 100644 --- a/WEB-INF/lib/pear/PEAR/Installer/Role/Php.php +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Php.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Php.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -20,7 +19,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Script.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Script.php index b31469e4b..f1eeda0b6 100644 --- a/WEB-INF/lib/pear/PEAR/Installer/Role/Script.php +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Script.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Script.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -20,7 +19,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Src.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Src.php index 503705313..2c7ae2142 100644 --- a/WEB-INF/lib/pear/PEAR/Installer/Role/Src.php +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Src.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Src.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -20,7 +19,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Test.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Test.php index 14c0e6091..c19c5dc06 100644 --- a/WEB-INF/lib/pear/PEAR/Installer/Role/Test.php +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Test.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Test.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -20,7 +19,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ diff --git a/WEB-INF/lib/pear/PEAR/Installer/Role/Www.php b/WEB-INF/lib/pear/PEAR/Installer/Role/Www.php index 11adeff82..42b197a90 100644 --- a/WEB-INF/lib/pear/PEAR/Installer/Role/Www.php +++ b/WEB-INF/lib/pear/PEAR/Installer/Role/Www.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 2007-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Www.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.7.0 */ @@ -20,7 +19,7 @@ * @author Greg Beaver * @copyright 2007-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.7.0 */ diff --git a/WEB-INF/lib/pear/PEAR/PackageFile.php b/WEB-INF/lib/pear/PEAR/PackageFile.php index 7ae336284..8fb6e41fa 100644 --- a/WEB-INF/lib/pear/PEAR/PackageFile.php +++ b/WEB-INF/lib/pear/PEAR/PackageFile.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: PackageFile.php 313024 2011-07-06 19:51:24Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -35,7 +34,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -67,7 +66,7 @@ class PEAR_PackageFile * @param string @tmpdir Optional temporary directory for uncompressing * files */ - function PEAR_PackageFile(&$config, $debug = false) + function __construct(&$config, $debug = false) { $this->_config = $config; $this->_debug = $debug; @@ -489,4 +488,4 @@ function &fromAnyFile($info, $state) $info = PEAR::raiseError("Cannot open '$info' for parsing"); return $info; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v1.php b/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v1.php index 2f42f178d..5a963787c 100644 --- a/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v1.php +++ b/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v1.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: v1.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -29,7 +28,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -39,14 +38,14 @@ class PEAR_PackageFile_Generator_v1 * @var PEAR_PackageFile_v1 */ var $_packagefile; - function PEAR_PackageFile_Generator_v1(&$packagefile) + function __construct(&$packagefile) { $this->_packagefile = &$packagefile; } function getPackagerVersion() { - return '1.9.4'; + return '1.10.1'; } /** @@ -109,7 +108,7 @@ function toTgz(&$packager, $compress = true, $where = null) // }}} $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true); if ($packagexml) { - $tar =& new Archive_Tar($dest_package, $compress); + $tar = new Archive_Tar($dest_package, $compress); $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors // ----- Creates with the package.xml file $ok = $tar->createModify(array($packagexml), '', $where); @@ -168,9 +167,6 @@ function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'pa */ function _fixXmlEncoding($string) { - if (version_compare(phpversion(), '5.0.0', 'lt')) { - $string = utf8_encode($string); - } return strtr($string, array( '&' => '&', '>' => '>', @@ -200,7 +196,7 @@ function toXml($state = PEAR_VALIDATE_NORMAL, $nofilevalidation = false) ); $ret = "\n"; $ret .= "\n"; - $ret .= "\n" . + $ret .= "\n" . " $pkginfo[package]"; if (isset($pkginfo['extends'])) { $ret .= "\n$pkginfo[extends]"; @@ -1178,13 +1174,15 @@ function _processPhpDeps($deps) } if (count($min)) { // get the highest minimum - $min = array_pop($a = array_flip($min)); + $a = array_flip($min); + $min = array_pop($a); } else { $min = false; } if (count($max)) { // get the lowest maximum - $max = array_shift($a = array_flip($max)); + $a = array_flip($max); + $max = array_shift($a); } else { $max = false; } @@ -1250,13 +1248,15 @@ function _processMultipleDepsName($deps) } if (count($min)) { // get the highest minimum - $min = array_pop($a = array_flip($min)); + $a = array_flip($min); + $min = array_pop($a); } else { $min = false; } if (count($max)) { // get the lowest maximum - $max = array_shift($a = array_flip($max)); + $a = array_flip($max); + $max = array_shift($a); } else { $max = false; } @@ -1281,4 +1281,4 @@ function _processMultipleDepsName($deps) return $ret; } } -?> \ No newline at end of file +?> diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v2.php b/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v2.php index 4d202df27..24e89f3aa 100644 --- a/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v2.php +++ b/WEB-INF/lib/pear/PEAR/PackageFile/Generator/v2.php @@ -10,7 +10,6 @@ * @author Stephan Schmidt (original XML_Serializer code) * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: v2.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -31,7 +30,7 @@ * @author Stephan Schmidt (original XML_Serializer code) * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -70,7 +69,7 @@ class PEAR_PackageFile_Generator_v2 http://pear.php.net/dtd/package-2.0.xsd', ), // attributes of the root tag 'attributesArray' => 'attribs', // all values in this key will be treated as attributes - 'contentName' => '_content', // this value will be used directly as content, instead of creating a new tag, may only be used in conjuction with attributesArray + 'contentName' => '_content', // this value will be used directly as content, instead of creating a new tag, may only be used in conjunction with attributesArray 'beautifyFilelist' => false, 'encoding' => 'UTF-8', ); @@ -100,7 +99,7 @@ class PEAR_PackageFile_Generator_v2 /** * @param PEAR_PackageFile_v2 */ - function PEAR_PackageFile_Generator_v2(&$packagefile) + function __construct(&$packagefile) { $this->_packagefile = &$packagefile; if (isset($this->_packagefile->encoding)) { @@ -113,7 +112,7 @@ function PEAR_PackageFile_Generator_v2(&$packagefile) */ function getPackagerVersion() { - return '1.9.4'; + return '1.10.1'; } /** @@ -232,7 +231,7 @@ function toTgz2(&$packager, &$pf1, $compress = true, $where = null) array($this->_packagefile->getTasksNs() . ':', '-'), array('', '_'), $tag); $task = "PEAR_Task_$tag"; - $task = &new $task($this->_packagefile->_config, + $task = new $task($this->_packagefile->_config, $this->_packagefile->_logger, PEAR_TASK_PACKAGE); $task->init($raw, $atts, null); @@ -269,7 +268,7 @@ function toTgz2(&$packager, &$pf1, $compress = true, $where = null) $name = $pf1 !== null ? 'package2.xml' : 'package.xml'; $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, $name); if ($packagexml) { - $tar =& new Archive_Tar($dest_package, $compress); + $tar = new Archive_Tar($dest_package, $compress); $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors // ----- Creates with the package.xml file $ok = $tar->createModify(array($packagexml), '', $where); @@ -398,7 +397,7 @@ function toXml($state = PEAR_VALIDATE_NORMAL, $options = array()) $this->options['beautifyFilelist'] = true; } - $arr['attribs']['packagerversion'] = '1.9.4'; + $arr['attribs']['packagerversion'] = '1.10.1'; if ($this->serialize($arr, $options)) { return $this->_serializedData . "\n"; } @@ -869,12 +868,6 @@ function _createXMLTag($tag, $replaceEntities = true) } if (is_scalar($tag['content']) || is_null($tag['content'])) { - if ($this->options['encoding'] == 'UTF-8' && - version_compare(phpversion(), '5.0.0', 'lt') - ) { - $tag['content'] = utf8_encode($tag['content']); - } - if ($replaceEntities === true) { $replaceEntities = XML_UTIL_ENTITIES_XML; } @@ -890,4 +883,4 @@ function _createXMLTag($tag, $replaceEntities = true) } return $tag; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v1.php b/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v1.php index 23395dc75..8e08e0b42 100644 --- a/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v1.php +++ b/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v1.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: v1.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -91,7 +90,7 @@ function &parse($data, $file, $archive = false) $code = xml_get_error_code($xp); $line = xml_get_current_line_number($xp); xml_parser_free($xp); - $a = &PEAR::raiseError(sprintf("XML error: %s at line %d", + $a = PEAR::raiseError(sprintf("XML error: %s at line %d", $str = xml_error_string($code), $line), 2); return $a; } diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v2.php b/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v2.php index a3ba7063f..49a29f1aa 100644 --- a/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v2.php +++ b/WEB-INF/lib/pear/PEAR/PackageFile/Parser/v2.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: v2.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -93,9 +92,9 @@ function postProcess($data, $element) * a subclass * @return PEAR_PackageFile_v2 */ - function &parse($data, $file, $archive = false, $class = 'PEAR_PackageFile_v2') + function parse($data, $file = null, $archive = false, $class = 'PEAR_PackageFile_v2') { - if (PEAR::isError($err = parent::parse($data, $file))) { + if (PEAR::isError($err = parent::parse($data))) { return $err; } diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/v1.php b/WEB-INF/lib/pear/PEAR/PackageFile/v1.php index 43e346bcd..413db67d1 100644 --- a/WEB-INF/lib/pear/PEAR/PackageFile/v1.php +++ b/WEB-INF/lib/pear/PEAR/PackageFile/v1.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: v1.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -275,7 +274,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -346,9 +345,9 @@ class PEAR_PackageFile_v1 * @param bool determines whether to return a PEAR_Error object, or use the PEAR_ErrorStack * @param string Name of Error Stack class to use. */ - function PEAR_PackageFile_v1() + function __construct() { - $this->_stack = &new PEAR_ErrorStack('PEAR_PackageFile_v1'); + $this->_stack = new PEAR_ErrorStack('PEAR_PackageFile_v1'); $this->_stack->setErrorMessageTemplate($this->_getErrorMessage()); $this->_isValid = 0; } @@ -1308,7 +1307,7 @@ function &getDefaultGenerator() if (!class_exists('PEAR_PackageFile_Generator_v1')) { require_once 'PEAR/PackageFile/Generator/v1.php'; } - $a = &new PEAR_PackageFile_Generator_v1($this); + $a = new PEAR_PackageFile_Generator_v1($this); return $a; } @@ -1331,7 +1330,7 @@ function getFileContents($file) if (!class_exists('Archive_Tar')) { require_once 'Archive/Tar.php'; } - $tar = &new Archive_Tar($this->_archiveFile); + $tar = new Archive_Tar($this->_archiveFile); $tar->pushErrorHandling(PEAR_ERROR_RETURN); if ($file != 'package.xml' && $file != 'package2.xml') { $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; @@ -1466,15 +1465,6 @@ function _analyzeSourceCode($file) $look_for = $token; continue 2; case T_STRING: - if (version_compare(zend_version(), '2.0', '<')) { - if (in_array(strtolower($data), - array('public', 'private', 'protected', 'abstract', - 'interface', 'implements', 'throw') - )) { - $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_PHP5, - array($file)); - } - } if ($look_for == T_CLASS) { $current_class = $data; $current_class_level = $brace_level; diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/v2.php b/WEB-INF/lib/pear/PEAR/PackageFile/v2.php index 1ca412dc8..ae0a1fa89 100644 --- a/WEB-INF/lib/pear/PEAR/PackageFile/v2.php +++ b/WEB-INF/lib/pear/PEAR/PackageFile/v2.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: v2.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -23,7 +22,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -129,12 +128,21 @@ class PEAR_PackageFile_v2 /** * The constructor merely sets up the private error stack */ - function PEAR_PackageFile_v2() + function __construct() { $this->_stack = new PEAR_ErrorStack('PEAR_PackageFile_v2', false, null); $this->_isValid = false; } + /** + * PHP 4 style constructor for backwards compatibility. + * Used by PEAR_PackageFileManager2 + */ + public function PEAR_PackageFile_v2() + { + $this->__construct(); + } + /** * To make unit-testing easier * @param PEAR_Frontend_* @@ -145,7 +153,7 @@ function PEAR_PackageFile_v2() */ function &getPEARDownloader(&$i, $o, &$c) { - $z = &new PEAR_Downloader($i, $o, $c); + $z = new PEAR_Downloader($i, $o, $c); return $z; } @@ -163,7 +171,7 @@ function &getPEARDependency2(&$c, $o, $p, $s = PEAR_VALIDATE_INSTALLING) if (!class_exists('PEAR_Dependency2')) { require_once 'PEAR/Dependency2.php'; } - $z = &new PEAR_Dependency2($c, $o, $p, $s); + $z = new PEAR_Dependency2($c, $o, $p, $s); return $z; } @@ -564,7 +572,7 @@ function listPostinstallScripts() $atts = $filelist[$name]; foreach ($tasks as $tag => $raw) { $task = $this->getTask($tag); - $task = &new $task($this->_config, $common, PEAR_TASK_INSTALL); + $task = new $task($this->_config, $common, PEAR_TASK_INSTALL); if ($task->isScript()) { $ret[] = $filelist[$name]['installed_as']; } @@ -610,7 +618,7 @@ function initPostinstallScripts() $atts = $filelist[$name]; foreach ($tasks as $tag => $raw) { $taskname = $this->getTask($tag); - $task = &new $taskname($this->_config, $common, PEAR_TASK_INSTALL); + $task = new $taskname($this->_config, $common, PEAR_TASK_INSTALL); if (!$task->isScript()) { continue; // scripts are only handled after installation } @@ -1177,6 +1185,9 @@ function getFilelist($preserve = false) $contents['dir']['file'] = array($contents['dir']['file']); } foreach ($contents['dir']['file'] as $file) { + if (!isset($file['attribs']['name'])) { + continue; + } $name = $file['attribs']['name']; if (!$preserve) { $file = $file['attribs']; @@ -1854,7 +1865,7 @@ function getFileContents($file) return implode('', file($file)); } } else { // tgz - $tar = &new Archive_Tar($this->_archiveFile); + $tar = new Archive_Tar($this->_archiveFile); $tar->pushErrorHandling(PEAR_ERROR_RETURN); if ($file != 'package.xml' && $file != 'package2.xml') { $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file; @@ -1893,7 +1904,7 @@ function &getDefaultGenerator() if (!class_exists('PEAR_PackageFile_Generator_v2')) { require_once 'PEAR/PackageFile/Generator/v2.php'; } - $a = &new PEAR_PackageFile_Generator_v2($this); + $a = new PEAR_PackageFile_Generator_v2($this); return $a; } diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/v2/Validator.php b/WEB-INF/lib/pear/PEAR/PackageFile/v2/Validator.php index 33c8eee38..eff9d03ca 100644 --- a/WEB-INF/lib/pear/PEAR/PackageFile/v2/Validator.php +++ b/WEB-INF/lib/pear/PEAR/PackageFile/v2/Validator.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Validator.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a8 */ @@ -21,7 +20,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a8 * @access private @@ -112,7 +111,8 @@ function validate(&$pf, $state = PEAR_VALIDATE_NORMAL) isset($test['dependencies']['required']) && isset($test['dependencies']['required']['pearinstaller']) && isset($test['dependencies']['required']['pearinstaller']['min']) && - version_compare('1.9.4', + '1.10.1' != '@package' . '_version@' && + version_compare('1.10.1', $test['dependencies']['required']['pearinstaller']['min'], '<') ) { $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']); @@ -1350,7 +1350,7 @@ function _pearVersionTooLow($version) $this->_stack->push(__FUNCTION__, 'error', array('version' => $version), 'This package.xml requires PEAR version %version% to parse properly, we are ' . - 'version 1.9.4'); + 'version 1.10.1'); } function _invalidTagOrder($oktags, $actual, $root) @@ -1737,7 +1737,7 @@ function _analyzeBundledPackages() if (!is_array($info)) { $info = array($info); } - $pkg = &new PEAR_PackageFile($this->_pf->_config); + $pkg = new PEAR_PackageFile($this->_pf->_config); foreach ($info as $package) { if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) { $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package); @@ -1875,7 +1875,7 @@ function analyzeSourceCode($file, $string = false) $pn = $this->_pf->getPackage(); $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file, 'package' => $pn), - 'in %file%: Could not process file for unkown reasons,' . + 'in %file%: Could not process file for unknown reasons,' . ' possibly a PHP parse error in %file% from %package%'); } } @@ -1980,25 +1980,6 @@ function analyzeSourceCode($file, $string = false) $look_for = $token; continue 2; case T_STRING: - if (version_compare(zend_version(), '2.0', '<')) { - if (in_array(strtolower($data), - array('public', 'private', 'protected', 'abstract', - 'interface', 'implements', 'throw') - ) - ) { - if (isset($this->_stack)) { - $this->_stack->push(__FUNCTION__, 'warning', array( - 'file' => $file), - 'Error, PHP5 token encountered in %file%,' . - ' analysis should be in PHP5'); - } else { - PEAR::raiseError('Error: PHP5 token encountered in ' . $file . - 'packaging should be done in PHP 5'); - return false; - } - } - } - if ($look_for == T_CLASS) { $current_class = $data; $current_class_level = $brace_level; @@ -2045,7 +2026,7 @@ function analyzeSourceCode($file, $string = false) continue 2; case T_DOUBLE_COLON: $token = $tokens[$i - 1][0]; - if (!($token == T_WHITESPACE || $token == T_STRING || $token == T_STATIC)) { + if (!($token == T_WHITESPACE || $token == T_STRING || $token == T_STATIC || $token == T_VARIABLE)) { if (isset($this->_stack)) { $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file), 'Parser error: invalid PHP found in file "%file%"'); @@ -2151,4 +2132,4 @@ function _buildProvidesArray($srcinfo) return $providesret; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/PackageFile/v2/rw.php b/WEB-INF/lib/pear/PEAR/PackageFile/v2/rw.php index 58f76c559..f2b58e396 100644 --- a/WEB-INF/lib/pear/PEAR/PackageFile/v2/rw.php +++ b/WEB-INF/lib/pear/PEAR/PackageFile/v2/rw.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a8 */ @@ -23,7 +22,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a8 */ @@ -242,7 +241,7 @@ function updateMaintainer($newrole, $handle, $name, $email, $active = 'yes') } } foreach ($info as $i => $maintainer) { - if ($maintainer['user'] == $handle) { + if (is_array($maintainer) && $maintainer['user'] == $handle) { $found = $i; break 2; } diff --git a/WEB-INF/lib/pear/PEAR/Packager.php b/WEB-INF/lib/pear/PEAR/Packager.php index 8995a167f..3303f4c10 100644 --- a/WEB-INF/lib/pear/PEAR/Packager.php +++ b/WEB-INF/lib/pear/PEAR/Packager.php @@ -11,7 +11,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Packager.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -31,7 +30,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 */ @@ -50,7 +49,7 @@ function package($pkgfile = null, $compress = true, $pkg2 = null) } PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN); - $pkg = &new PEAR_PackageFile($this->config, $this->debug); + $pkg = new PEAR_PackageFile($this->config, $this->debug); $pf = &$pkg->fromPackageFile($pkgfile, PEAR_VALIDATE_NORMAL); $main = &$pf; PEAR::staticPopErrorHandling(); diff --git a/WEB-INF/lib/pear/PEAR/REST.php b/WEB-INF/lib/pear/PEAR/REST.php index 34a804f2b..c0dfeaa69 100644 --- a/WEB-INF/lib/pear/PEAR/REST.php +++ b/WEB-INF/lib/pear/PEAR/REST.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: REST.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -28,7 +27,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -37,7 +36,7 @@ class PEAR_REST var $config; var $_options; - function PEAR_REST(&$config, $options = array()) + function __construct(&$config, $options = array()) { $this->config = &$config; $this->_options = $options; @@ -129,11 +128,13 @@ function retrieveData($url, $accept = false, $forcestring = false, $channel = fa } if (isset($headers['content-type'])) { - switch ($headers['content-type']) { + $content_type = explode(";", $headers['content-type']); + $content_type = $content_type[0]; + switch ($content_type) { case 'text/xml' : case 'application/xml' : case 'text/plain' : - if ($headers['content-type'] === 'text/plain') { + if ($content_type === 'text/plain') { $check = substr($content, 0, 5); if ($check !== 'config->get('username', null, $channel); $password = $this->config->get('password', null, $channel); @@ -480,4 +493,4 @@ function downloadHttp($url, $lastmodified = null, $accept = false, $channel = fa return $data; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/REST/10.php b/WEB-INF/lib/pear/PEAR/REST/10.php index 6ded7aeac..affcc18ee 100644 --- a/WEB-INF/lib/pear/PEAR/REST/10.php +++ b/WEB-INF/lib/pear/PEAR/REST/10.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: 10.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a12 */ @@ -27,7 +26,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a12 */ @@ -37,9 +36,9 @@ class PEAR_REST_10 * @var PEAR_REST */ var $_rest; - function PEAR_REST_10($config, $options = array()) + function __construct($config, $options = array()) { - $this->_rest = &new PEAR_REST($config, $options); + $this->_rest = new PEAR_REST($config, $options); } /** @@ -868,4 +867,4 @@ function _sortReleasesByVersionNumber($a, $b) return 1; } } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/REST/11.php b/WEB-INF/lib/pear/PEAR/REST/11.php index 831cfccdb..9bd51ba6f 100644 --- a/WEB-INF/lib/pear/PEAR/REST/11.php +++ b/WEB-INF/lib/pear/PEAR/REST/11.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: 11.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.3 */ @@ -27,7 +26,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.3 */ @@ -38,9 +37,9 @@ class PEAR_REST_11 */ var $_rest; - function PEAR_REST_11($config, $options = array()) + function __construct($config, $options = array()) { - $this->_rest = &new PEAR_REST($config, $options); + $this->_rest = new PEAR_REST($config, $options); } function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false) @@ -338,4 +337,4 @@ function betterStates($state, $include = false) return array_slice($states, $i + 1); } } -?> \ No newline at end of file +?> diff --git a/WEB-INF/lib/pear/PEAR/REST/13.php b/WEB-INF/lib/pear/PEAR/REST/13.php index 722ae0de3..3855c6e05 100644 --- a/WEB-INF/lib/pear/PEAR/REST/13.php +++ b/WEB-INF/lib/pear/PEAR/REST/13.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: 13.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a12 */ @@ -28,7 +27,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a12 */ @@ -296,4 +295,102 @@ function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage, return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel); } + + /** + * List package upgrades but take the PHP version into account. + */ + function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg) + { + $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel); + if (PEAR::isError($packagelist)) { + return $packagelist; + } + + $ret = array(); + if (!is_array($packagelist) || !isset($packagelist['p'])) { + return $ret; + } + + if (!is_array($packagelist['p'])) { + $packagelist['p'] = array($packagelist['p']); + } + + foreach ($packagelist['p'] as $package) { + if (!isset($installed[strtolower($package)])) { + continue; + } + + $inst_version = $reg->packageInfo($package, 'version', $channel); + $inst_state = $reg->packageInfo($package, 'release_state', $channel); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $info = $this->_rest->retrieveData($base . 'r/' . strtolower($package) . + '/allreleases2.xml', false, false, $channel); + PEAR::popErrorHandling(); + if (PEAR::isError($info)) { + continue; // no remote releases + } + + if (!isset($info['r'])) { + continue; + } + + $release = $found = false; + if (!is_array($info['r']) || !isset($info['r'][0])) { + $info['r'] = array($info['r']); + } + + // $info['r'] is sorted by version number + usort($info['r'], array($this, '_sortReleasesByVersionNumber')); + foreach ($info['r'] as $release) { + if ($inst_version && version_compare($release['v'], $inst_version, '<=')) { + // not newer than the one installed + break; + } + if (version_compare($release['m'], phpversion(), '>')) { + // skip dependency releases that require a PHP version + // newer than our PHP version + continue; + } + + // new version > installed version + if (!$pref_state) { + // every state is a good state + $found = true; + break; + } else { + $new_state = $release['s']; + // if new state >= installed state: go + if (in_array($new_state, $this->betterStates($inst_state, true))) { + $found = true; + break; + } else { + // only allow to lower the state of package, + // if new state >= preferred state: go + if (in_array($new_state, $this->betterStates($pref_state, true))) { + $found = true; + break; + } + } + } + } + + if (!$found) { + continue; + } + + $relinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/' . + $release['v'] . '.xml', false, false, $channel); + if (PEAR::isError($relinfo)) { + return $relinfo; + } + + $ret[$package] = array( + 'version' => $release['v'], + 'state' => $release['s'], + 'filesize' => $relinfo['f'], + ); + } + + return $ret; + } } \ No newline at end of file diff --git a/WEB-INF/lib/pear/PEAR/Registry.php b/WEB-INF/lib/pear/PEAR/Registry.php index 35e17db49..c22d82f4a 100644 --- a/WEB-INF/lib/pear/PEAR/Registry.php +++ b/WEB-INF/lib/pear/PEAR/Registry.php @@ -11,7 +11,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Registry.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -37,7 +36,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -131,24 +130,27 @@ class PEAR_Registry extends PEAR * * @access public */ - function PEAR_Registry($pear_install_dir = PEAR_INSTALL_DIR, $pear_channel = false, - $pecl_channel = false) + function __construct($pear_install_dir = PEAR_INSTALL_DIR, $pear_channel = false, + $pecl_channel = false, $pear_metadata_dir = '') { - parent::PEAR(); - $this->setInstallDir($pear_install_dir); + parent::__construct(); + $this->setInstallDir($pear_install_dir, $pear_metadata_dir); $this->_pearChannel = $pear_channel; $this->_peclChannel = $pecl_channel; $this->_config = false; } - function setInstallDir($pear_install_dir = PEAR_INSTALL_DIR) + function setInstallDir($pear_install_dir = PEAR_INSTALL_DIR, $pear_metadata_dir = '') { $ds = DIRECTORY_SEPARATOR; $this->install_dir = $pear_install_dir; - $this->channelsdir = $pear_install_dir.$ds.'.channels'; - $this->statedir = $pear_install_dir.$ds.'.registry'; - $this->filemap = $pear_install_dir.$ds.'.filemap'; - $this->lockfile = $pear_install_dir.$ds.'.lock'; + if (!$pear_metadata_dir) { + $pear_metadata_dir = $pear_install_dir; + } + $this->channelsdir = $pear_metadata_dir.$ds.'.channels'; + $this->statedir = $pear_metadata_dir.$ds.'.registry'; + $this->filemap = $pear_metadata_dir.$ds.'.filemap'; + $this->lockfile = $pear_metadata_dir.$ds.'.lock'; } function hasWriteAccess() @@ -181,7 +183,7 @@ function setConfig(&$config, $resetInstallDir = true) { $this->_config = &$config; if ($resetInstallDir) { - $this->setInstallDir($config->get('php_dir')); + $this->setInstallDir($config->get('php_dir'), $config->get('metadata_dir')); } } @@ -319,7 +321,7 @@ function _initializeDepDB() $initializing = true; if (!$this->_config) { // never used? $file = OS_WINDOWS ? 'pear.ini' : '.pearrc'; - $this->_config = &new PEAR_Config($this->statedir . DIRECTORY_SEPARATOR . + $this->_config = new PEAR_Config($this->statedir . DIRECTORY_SEPARATOR . $file); $this->_config->setRegistry($this); $this->_config->set('php_dir', $this->install_dir); @@ -328,9 +330,9 @@ function _initializeDepDB() $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config); if (PEAR::isError($this->_dependencyDB)) { // attempt to recover by removing the dep db - if (file_exists($this->_config->get('php_dir', null, 'pear.php.net') . + if (file_exists($this->_config->get('metadata_dir', null, 'pear.php.net') . DIRECTORY_SEPARATOR . '.depdb')) { - @unlink($this->_config->get('php_dir', null, 'pear.php.net') . + @unlink($this->_config->get('metadata_dir', null, 'pear.php.net') . DIRECTORY_SEPARATOR . '.depdb'); } @@ -782,12 +784,9 @@ function _readFileMap() } clearstatcache(); - $rt = get_magic_quotes_runtime(); - set_magic_quotes_runtime(0); $fsize = filesize($this->filemap); fclose($fp); $data = file_get_contents($this->filemap); - set_magic_quotes_runtime($rt); $tmp = unserialize($data); if (!$tmp && $fsize > 7) { return $this->raiseError('PEAR_Registry: invalid filemap data', PEAR_REGISTRY_ERROR_FORMAT, null, null, $data); @@ -1136,12 +1135,9 @@ function _packageInfo($package = null, $key = null, $channel = 'pear.php.net') return null; } - $rt = get_magic_quotes_runtime(); - set_magic_quotes_runtime(0); clearstatcache(); $this->_closePackageFile($fp); $data = file_get_contents($this->_packageFileName($package, $channel)); - set_magic_quotes_runtime($rt); $data = unserialize($data); if ($key === null) { return $data; @@ -1175,12 +1171,9 @@ function _channelInfo($channel, $noaliases = false) return null; } - $rt = get_magic_quotes_runtime(); - set_magic_quotes_runtime(0); clearstatcache(); $this->_closeChannelFile($fp); $data = file_get_contents($this->_channelFileName($channel)); - set_magic_quotes_runtime($rt); $data = unserialize($data); return $data; } @@ -1447,7 +1440,7 @@ function &_getPackage($package, $channel = 'pear.php.net') $a = $this->_config; if (!$a) { - $this->_config = &new PEAR_Config; + $this->_config = new PEAR_Config; $this->_config->set('php_dir', $this->statedir); } @@ -1455,7 +1448,7 @@ function &_getPackage($package, $channel = 'pear.php.net') require_once 'PEAR/PackageFile.php'; } - $pkg = &new PEAR_PackageFile($this->_config); + $pkg = new PEAR_PackageFile($this->_config); $pf = &$pkg->fromArray($info); return $pf; } @@ -1934,12 +1927,12 @@ function updatePackage2($info) * @param bool whether to strictly return raw channels (no aliases) * @return PEAR_ChannelFile|PEAR_Error */ - function &getChannel($channel, $noaliases = false) + function getChannel($channel, $noaliases = false) { if (PEAR::isError($e = $this->_lock(LOCK_SH))) { return $e; } - $ret = &$this->_getChannel($channel, $noaliases); + $ret = $this->_getChannel($channel, $noaliases); $this->_unlock(); if (!$ret) { return PEAR::raiseError('Unknown channel: ' . $channel); @@ -2392,4 +2385,4 @@ function parsedPackageNameToString($parsed, $brief = false) } return $ret; } -} \ No newline at end of file +} diff --git a/WEB-INF/lib/pear/PEAR/RunTest.php b/WEB-INF/lib/pear/PEAR/RunTest.php index 518249069..59dedbf9b 100644 --- a/WEB-INF/lib/pear/PEAR/RunTest.php +++ b/WEB-INF/lib/pear/PEAR/RunTest.php @@ -10,7 +10,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: RunTest.php 313024 2011-07-06 19:51:24Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.3.3 */ @@ -38,7 +37,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.3.3 */ @@ -60,7 +59,6 @@ class PEAR_RunTest var $ini_overwrites = array( 'output_handler=', 'open_basedir=', - 'safe_mode=0', 'disable_functions=', 'output_buffering=Off', 'display_errors=1', @@ -75,7 +73,6 @@ class PEAR_RunTest 'error_append_string=', 'auto_prepend_file=', 'auto_append_file=', - 'magic_quotes_runtime=0', 'xdebug.default_enable=0', 'allow_url_fopen=1', ); @@ -84,7 +81,7 @@ class PEAR_RunTest * An object that supports the PEAR_Common->log() signature, or null * @param PEAR_Common|null */ - function PEAR_RunTest($logger = null, $options = array()) + function __construct($logger = null, $options = array()) { if (!defined('E_DEPRECATED')) { define('E_DEPRECATED', 0); @@ -115,19 +112,11 @@ function PEAR_RunTest($logger = null, $options = array()) function system_with_timeout($commandline, $env = null, $stdin = null) { $data = ''; - if (version_compare(phpversion(), '5.0.0', '<')) { - $proc = proc_open($commandline, array( - 0 => array('pipe', 'r'), - 1 => array('pipe', 'w'), - 2 => array('pipe', 'w') - ), $pipes); - } else { - $proc = proc_open($commandline, array( - 0 => array('pipe', 'r'), - 1 => array('pipe', 'w'), - 2 => array('pipe', 'w') - ), $pipes, null, $env, array('suppress_errors' => true)); - } + $proc = proc_open($commandline, array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ), $pipes, null, $env, array('suppress_errors' => true)); if (!$proc) { return false; @@ -231,12 +220,7 @@ function settings2params($ini_settings) function _preparePhpBin($php, $file, $ini_settings) { $file = escapeshellarg($file); - // This was fixed in php 5.3 and is not needed after that - if (OS_WINDOWS && version_compare(PHP_VERSION, '5.3', '<')) { - $cmd = '"'.escapeshellarg($php).' '.$ini_settings.' -f ' . $file .'"'; - } else { - $cmd = $php . $ini_settings . ' -f ' . $file; - } + $cmd = $php . $ini_settings . ' -f ' . $file; return $cmd; } @@ -275,10 +259,8 @@ function runPHPUnit($file, $ini_settings = '') */ function run($file, $ini_settings = array(), $test_number = 1) { - if (isset($this->_savephp)) { - $this->_php = $this->_savephp; - unset($this->_savephp); - } + $this->_restorePHPBinary(); + if (empty($this->_options['cgi'])) { // try to see if php-cgi is in the path $res = $this->system_with_timeout('php-cgi -v'); @@ -340,7 +322,7 @@ function run($file, $ini_settings = array(), $test_number = 1) } return 'SKIPPED'; } - $this->_savephp = $this->_php; + $this->_savePHPBinary(); $this->_php = $this->_options['cgi']; } @@ -494,8 +476,6 @@ function run($file, $ini_settings = array(), $test_number = 1) } chdir($cwd); // in case the test moves us around - $this->_testCleanup($section_text, $temp_clean); - /* when using CGI, strip the headers from the output */ $output = $this->_stripHeadersCGI($output); @@ -516,6 +496,9 @@ function run($file, $ini_settings = array(), $test_number = 1) $output .= "\n====EXPECTHEADERS FAILURE====:\n$changed"; } } + + $this->_testCleanup($section_text, $temp_clean); + // Does the output match what is expected? do { if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { @@ -639,6 +622,11 @@ function run($file, $ini_settings = array(), $test_number = 1) $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null; $data = $this->generate_diff($wanted, $output, $returns, $expectf); $res = $this->_writeLog($diff_filename, $data); + if (isset($this->_options['showdiff'])) { + $this->_logger->log(0, "========DIFF========"); + $this->_logger->log(0, $data); + $this->_logger->log(0, "========DONE========"); + } if (PEAR::isError($res)) { return $res; } @@ -954,6 +942,8 @@ function _processUpload($section_text, $file) function _testCleanup($section_text, $temp_clean) { if ($section_text['CLEAN']) { + $this->_restorePHPBinary(); + // perform test cleanup $this->save_text($temp_clean, $section_text['CLEAN']); $output = $this->system_with_timeout("$this->_php $temp_clean 2>&1"); @@ -965,4 +955,18 @@ function _testCleanup($section_text, $temp_clean) } } } + + function _savePHPBinary() + { + $this->_savephp = $this->_php; + } + + function _restorePHPBinary() + { + if (isset($this->_savephp)) + { + $this->_php = $this->_savephp; + unset($this->_savephp); + } + } } diff --git a/WEB-INF/lib/pear/PEAR/Task/Common.php b/WEB-INF/lib/pear/PEAR/Task/Common.php index 5b99c2e43..ebb71dc8a 100644 --- a/WEB-INF/lib/pear/PEAR/Task/Common.php +++ b/WEB-INF/lib/pear/PEAR/Task/Common.php @@ -4,14 +4,13 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Common.php 313023 2011-07-06 19:17:11Z dufuz $ - * @link http://pear.php.net/package/PEAR - * @since File available since Release 1.4.0a1 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 */ /**#@+ * Error codes for task validation routines @@ -42,14 +41,15 @@ * This will first replace any instance of @data-dir@ in the test.php file * with the path to the current data directory. Then, it will include the * test.php file and run the script it contains to configure the package post-installation. - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 - * @link http://pear.php.net/package/PEAR - * @since Class available since Release 1.4.0a1 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.10.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 * @abstract */ class PEAR_Task_Common @@ -62,34 +62,35 @@ class PEAR_Task_Common * changes directly to disk * * Child task classes must override this property. + * * @access protected */ - var $type = 'simple'; + protected $type = 'simple'; /** * Determines which install phase this task is executed under */ - var $phase = PEAR_TASK_INSTALL; + public $phase = PEAR_TASK_INSTALL; /** * @access protected */ - var $config; + protected $config; /** * @access protected */ - var $registry; + protected $registry; /** * @access protected */ - var $logger; + public $logger; /** * @access protected */ - var $installphase; + protected $installphase; /** * @param PEAR_Config * @param PEAR_Common */ - function PEAR_Task_Common(&$config, &$logger, $phase) + function __construct(&$config, &$logger, $phase) { $this->config = &$config; $this->registry = &$config->getRegistry(); @@ -102,101 +103,105 @@ function PEAR_Task_Common(&$config, &$logger, $phase) /** * Validate the basic contents of a task tag. + * * @param PEAR_PackageFile_v2 * @param array * @param PEAR_Config * @param array the entire parsed tag + * * @return true|array On error, return an array in format: - * array(PEAR_TASK_ERROR_???[, param1][, param2][, ...]) + * array(PEAR_TASK_ERROR_???[, param1][, param2][, ...]) + * + * For PEAR_TASK_ERROR_MISSING_ATTRIB, pass the attribute name in + * For PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, pass the attribute name and + * an array of legal values in * - * For PEAR_TASK_ERROR_MISSING_ATTRIB, pass the attribute name in - * For PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, pass the attribute name and an array - * of legal values in - * @static * @abstract */ - function validateXml($pkg, $xml, $config, $fileXml) + public static function validateXml($pkg, $xml, $config, $fileXml) { } /** * Initialize a task instance with the parameters - * @param array raw, parsed xml - * @param array attributes from the tag containing this task - * @param string|null last installed version of this package + * + * @param array raw, parsed xml + * @param array attributes from the tag containing this task + * @param string|null last installed version of this package * @abstract */ - function init($xml, $fileAttributes, $lastVersion) + public function init($xml, $fileAttributes, $lastVersion) { } /** - * Begin a task processing session. All multiple tasks will be processed after each file - * has been successfully installed, all simple tasks should perform their task here and - * return any errors using the custom throwError() method to allow forward compatibility + * Begin a task processing session. All multiple tasks will be processed + * after each file has been successfully installed, all simple tasks should + * perform their task here and return any errors using the custom + * throwError() method to allow forward compatibility * * This method MUST NOT write out any changes to disk - * @param PEAR_PackageFile_v2 - * @param string file contents - * @param string the eventual final file location (informational only) - * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail - * (use $this->throwError), otherwise return the new contents + * + * @param PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) + * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail + * (use $this->throwError), otherwise return the new contents * @abstract */ - function startSession($pkg, $contents, $dest) + public function startSession($pkg, $contents, $dest) { } /** - * This method is used to process each of the tasks for a particular multiple class - * type. Simple tasks need not implement this method. - * @param array an array of tasks - * @access protected - * @static - * @abstract + * This method is used to process each of the tasks for a particular + * multiple class type. Simple tasks need not implement this method. + * + * @param array an array of tasks + * @access protected */ - function run($tasks) + public static function run($tasks) { } /** - * @static * @final */ - function hasPostinstallTasks() + public static function hasPostinstallTasks() { return isset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); } - /** - * @static - * @final - */ - function runPostinstallTasks() - { - foreach ($GLOBALS['_PEAR_TASK_POSTINSTANCES'] as $class => $tasks) { - $err = call_user_func(array($class, 'run'), - $GLOBALS['_PEAR_TASK_POSTINSTANCES'][$class]); - if ($err) { - return PEAR_Task_Common::throwError($err); - } - } - unset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); + /** + * @final + */ + public static function runPostinstallTasks() + { + foreach ($GLOBALS['_PEAR_TASK_POSTINSTANCES'] as $class => $tasks) { + $err = call_user_func( + array($class, 'run'), + $GLOBALS['_PEAR_TASK_POSTINSTANCES'][$class] + ); + if ($err) { + return PEAR_Task_Common::throwError($err); + } + } + unset($GLOBALS['_PEAR_TASK_POSTINSTANCES']); } /** * Determines whether a role is a script * @return bool */ - function isScript() + public function isScript() { - return $this->type == 'script'; + return $this->type == 'script'; } - function throwError($msg, $code = -1) + public function throwError($msg, $code = -1) { include_once 'PEAR.php'; + return PEAR::raiseError($msg, $code); } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/pear/PEAR/Task/Postinstallscript.php b/WEB-INF/lib/pear/PEAR/Task/Postinstallscript.php index e43ecca4b..950deb5ce 100644 --- a/WEB-INF/lib/pear/PEAR/Task/Postinstallscript.php +++ b/WEB-INF/lib/pear/PEAR/Task/Postinstallscript.php @@ -4,14 +4,13 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Postinstallscript.php 313023 2011-07-06 19:17:11Z dufuz $ - * @link http://pear.php.net/package/PEAR - * @since File available since Release 1.4.0a1 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 */ /** * Base class @@ -22,85 +21,95 @@ * * Note that post-install scripts are handled separately from installation, by the * "pear run-scripts" command - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 - * @link http://pear.php.net/package/PEAR - * @since Class available since Release 1.4.0a1 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.10.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 */ class PEAR_Task_Postinstallscript extends PEAR_Task_Common { - var $type = 'script'; - var $_class; - var $_params; - var $_obj; + public $type = 'script'; + public $_class; + public $_params; + public $_obj; /** * * @var PEAR_PackageFile_v2 */ - var $_pkg; - var $_contents; - var $phase = PEAR_TASK_INSTALL; + public $_pkg; + public $_contents; + public $phase = PEAR_TASK_INSTALL; /** * Validate the raw xml at parsing-time. * * This also attempts to validate the script to make sure it meets the criteria * for a post-install script - * @param PEAR_PackageFile_v2 - * @param array The XML contents of the tag - * @param PEAR_Config - * @param array the entire parsed tag - * @static + * + * @param PEAR_PackageFile_v2 + * @param array The XML contents of the tag + * @param PEAR_Config + * @param array the entire parsed tag */ - function validateXml($pkg, $xml, $config, $fileXml) + public static function validateXml($pkg, $xml, $config, $fileXml) { if ($fileXml['role'] != 'php') { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" must be role="php"'); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" must be role="php"', ); } PEAR::pushErrorHandling(PEAR_ERROR_RETURN); $file = $pkg->getFileContents($fileXml['name']); if (PEAR::isError($file)) { PEAR::popErrorHandling(); - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" is not valid: ' . - $file->getMessage()); + + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" is not valid: '. + $file->getMessage(), ); } elseif ($file === null) { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" could not be retrieved for processing!'); + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" could not be retrieved for processing!', ); } else { $analysis = $pkg->analyzeSourceCode($file, true); if (!$analysis) { PEAR::popErrorHandling(); $warnings = ''; foreach ($pkg->getValidationWarnings() as $warn) { - $warnings .= $warn['message'] . "\n"; + $warnings .= $warn['message']."\n"; } - return array(PEAR_TASK_ERROR_INVALID, 'Analysis of post-install script "' . - $fileXml['name'] . '" failed: ' . $warnings); + + return array(PEAR_TASK_ERROR_INVALID, 'Analysis of post-install script "'. + $fileXml['name'].'" failed: '.$warnings, ); } if (count($analysis['declared_classes']) != 1) { PEAR::popErrorHandling(); - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" must declare exactly 1 class'); + + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" must declare exactly 1 class', ); } $class = $analysis['declared_classes'][0]; - if ($class != str_replace(array('/', '.php'), array('_', ''), - $fileXml['name']) . '_postinstall') { + if ($class != str_replace( + array('/', '.php'), array('_', ''), + $fileXml['name'] + ).'_postinstall') { PEAR::popErrorHandling(); - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" class "' . $class . '" must be named "' . - str_replace(array('/', '.php'), array('_', ''), - $fileXml['name']) . '_postinstall"'); + + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" class "'.$class.'" must be named "'. + str_replace( + array('/', '.php'), array('_', ''), + $fileXml['name'] + ).'_postinstall"', ); } if (!isset($analysis['declared_methods'][$class])) { PEAR::popErrorHandling(); - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" must declare methods init() and run()'); + + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" must declare methods init() and run()', ); } $methods = array('init' => 0, 'run' => 1); foreach ($analysis['declared_methods'][$class] as $method) { @@ -110,129 +119,137 @@ function validateXml($pkg, $xml, $config, $fileXml) } if (count($methods)) { PEAR::popErrorHandling(); - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" must declare methods init() and run()'); + + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" must declare methods init() and run()', ); } } PEAR::popErrorHandling(); $definedparams = array(); - $tasksNamespace = $pkg->getTasksNs() . ':'; - if (!isset($xml[$tasksNamespace . 'paramgroup']) && isset($xml['paramgroup'])) { + $tasksNamespace = $pkg->getTasksNs().':'; + if (!isset($xml[$tasksNamespace.'paramgroup']) && isset($xml['paramgroup'])) { // in order to support the older betas, which did not expect internal tags // to also use the namespace $tasksNamespace = ''; } - if (isset($xml[$tasksNamespace . 'paramgroup'])) { - $params = $xml[$tasksNamespace . 'paramgroup']; + if (isset($xml[$tasksNamespace.'paramgroup'])) { + $params = $xml[$tasksNamespace.'paramgroup']; if (!is_array($params) || !isset($params[0])) { $params = array($params); } foreach ($params as $param) { - if (!isset($param[$tasksNamespace . 'id'])) { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" must have ' . - 'an ' . $tasksNamespace . 'id> tag'); + if (!isset($param[$tasksNamespace.'id'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" must have '. + 'an '.$tasksNamespace.'id> tag', ); } - if (isset($param[$tasksNamespace . 'name'])) { - if (!in_array($param[$tasksNamespace . 'name'], $definedparams)) { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" ' . $tasksNamespace . - 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . - '" parameter "' . $param[$tasksNamespace . 'name'] . - '" has not been previously defined'); + if (isset($param[$tasksNamespace.'name'])) { + if (!in_array($param[$tasksNamespace.'name'], $definedparams)) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" '.$tasksNamespace. + 'paramgroup> id "'.$param[$tasksNamespace.'id']. + '" parameter "'.$param[$tasksNamespace.'name']. + '" has not been previously defined', ); } - if (!isset($param[$tasksNamespace . 'conditiontype'])) { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" ' . $tasksNamespace . - 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . - '" must have a ' . $tasksNamespace . - 'conditiontype> tag containing either "=", ' . - '"!=", or "preg_match"'); + if (!isset($param[$tasksNamespace.'conditiontype'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" '.$tasksNamespace. + 'paramgroup> id "'.$param[$tasksNamespace.'id']. + '" must have a '.$tasksNamespace. + 'conditiontype> tag containing either "=", '. + '"!=", or "preg_match"', ); } - if (!in_array($param[$tasksNamespace . 'conditiontype'], - array('=', '!=', 'preg_match'))) { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" ' . $tasksNamespace . - 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . - '" must have a ' . $tasksNamespace . - 'conditiontype> tag containing either "=", ' . - '"!=", or "preg_match"'); + if (!in_array( + $param[$tasksNamespace.'conditiontype'], + array('=', '!=', 'preg_match') + )) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" '.$tasksNamespace. + 'paramgroup> id "'.$param[$tasksNamespace.'id']. + '" must have a '.$tasksNamespace. + 'conditiontype> tag containing either "=", '. + '"!=", or "preg_match"', ); } - if (!isset($param[$tasksNamespace . 'value'])) { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" ' . $tasksNamespace . - 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . - '" must have a ' . $tasksNamespace . - 'value> tag containing expected parameter value'); + if (!isset($param[$tasksNamespace.'value'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" '.$tasksNamespace. + 'paramgroup> id "'.$param[$tasksNamespace.'id']. + '" must have a '.$tasksNamespace. + 'value> tag containing expected parameter value', ); } } - if (isset($param[$tasksNamespace . 'instructions'])) { - if (!is_string($param[$tasksNamespace . 'instructions'])) { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" ' . $tasksNamespace . - 'paramgroup> id "' . $param[$tasksNamespace . 'id'] . - '" ' . $tasksNamespace . 'instructions> must be simple text'); + if (isset($param[$tasksNamespace.'instructions'])) { + if (!is_string($param[$tasksNamespace.'instructions'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" '.$tasksNamespace. + 'paramgroup> id "'.$param[$tasksNamespace.'id']. + '" '.$tasksNamespace.'instructions> must be simple text', ); } } - if (!isset($param[$tasksNamespace . 'param'])) { + if (!isset($param[$tasksNamespace.'param'])) { continue; // is no longer required } - $subparams = $param[$tasksNamespace . 'param']; + $subparams = $param[$tasksNamespace.'param']; if (!is_array($subparams) || !isset($subparams[0])) { $subparams = array($subparams); } foreach ($subparams as $subparam) { - if (!isset($subparam[$tasksNamespace . 'name'])) { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" parameter for ' . - $tasksNamespace . 'paramgroup> id "' . - $param[$tasksNamespace . 'id'] . '" must have ' . - 'a ' . $tasksNamespace . 'name> tag'); + if (!isset($subparam[$tasksNamespace.'name'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" parameter for '. + $tasksNamespace.'paramgroup> id "'. + $param[$tasksNamespace.'id'].'" must have '. + 'a '.$tasksNamespace.'name> tag', ); } - if (!preg_match('/[a-zA-Z0-9]+/', - $subparam[$tasksNamespace . 'name'])) { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" parameter "' . - $subparam[$tasksNamespace . 'name'] . - '" for ' . $tasksNamespace . 'paramgroup> id "' . - $param[$tasksNamespace . 'id'] . - '" is not a valid name. Must contain only alphanumeric characters'); + if (!preg_match( + '/[a-zA-Z0-9]+/', + $subparam[$tasksNamespace.'name'] + )) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" parameter "'. + $subparam[$tasksNamespace.'name']. + '" for '.$tasksNamespace.'paramgroup> id "'. + $param[$tasksNamespace.'id']. + '" is not a valid name. Must contain only alphanumeric characters', ); } - if (!isset($subparam[$tasksNamespace . 'prompt'])) { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" parameter "' . - $subparam[$tasksNamespace . 'name'] . - '" for ' . $tasksNamespace . 'paramgroup> id "' . - $param[$tasksNamespace . 'id'] . - '" must have a ' . $tasksNamespace . 'prompt> tag'); + if (!isset($subparam[$tasksNamespace.'prompt'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" parameter "'. + $subparam[$tasksNamespace.'name']. + '" for '.$tasksNamespace.'paramgroup> id "'. + $param[$tasksNamespace.'id']. + '" must have a '.$tasksNamespace.'prompt> tag', ); } - if (!isset($subparam[$tasksNamespace . 'type'])) { - return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' . - $fileXml['name'] . '" parameter "' . - $subparam[$tasksNamespace . 'name'] . - '" for ' . $tasksNamespace . 'paramgroup> id "' . - $param[$tasksNamespace . 'id'] . - '" must have a ' . $tasksNamespace . 'type> tag'); + if (!isset($subparam[$tasksNamespace.'type'])) { + return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "'. + $fileXml['name'].'" parameter "'. + $subparam[$tasksNamespace.'name']. + '" for '.$tasksNamespace.'paramgroup> id "'. + $param[$tasksNamespace.'id']. + '" must have a '.$tasksNamespace.'type> tag', ); } - $definedparams[] = $param[$tasksNamespace . 'id'] . '::' . - $subparam[$tasksNamespace . 'name']; + $definedparams[] = $param[$tasksNamespace.'id'].'::'. + $subparam[$tasksNamespace.'name']; } } } + return true; } /** * Initialize a task instance with the parameters - * @param array raw, parsed xml - * @param array attributes from the tag containing this task - * @param string|null last installed version of this package, if any (useful for upgrades) + * @param array $xml raw, parsed xml + * @param array $fileattribs attributes from the tag containing + * this task + * @param string|null $lastversion last installed version of this package, + * if any (useful for upgrades) */ - function init($xml, $fileattribs, $lastversion) + public function init($xml, $fileattribs, $lastversion) { $this->_class = str_replace('/', '_', $fileattribs['name']); $this->_filename = $fileattribs['name']; - $this->_class = str_replace ('.php', '', $this->_class) . '_postinstall'; + $this->_class = str_replace('.php', '', $this->_class).'_postinstall'; $this->_params = $xml; $this->_lastversion = $lastversion; } @@ -242,7 +259,7 @@ function init($xml, $fileattribs, $lastversion) * * @access private */ - function _stripNamespace($params = null) + public function _stripNamespace($params = null) { if ($params === null) { $params = array(); @@ -253,7 +270,7 @@ function _stripNamespace($params = null) if (is_array($param)) { $param = $this->_stripNamespace($param); } - $params[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + $params[str_replace($this->_pkg->getTasksNs().':', '', $i)] = $param; } $this->_params = $params; } else { @@ -262,21 +279,24 @@ function _stripNamespace($params = null) if (is_array($param)) { $param = $this->_stripNamespace($param); } - $newparams[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param; + $newparams[str_replace($this->_pkg->getTasksNs().':', '', $i)] = $param; } + return $newparams; } } /** - * Unlike other tasks, the installed file name is passed in instead of the file contents, - * because this task is handled post-installation - * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 - * @param string file name + * Unlike other tasks, the installed file name is passed in instead of the + * file contents, because this task is handled post-installation + * + * @param mixed $pkg PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string $contents file name + * * @return bool|PEAR_Error false to skip this file, PEAR_Error to fail - * (use $this->throwError) + * (use $this->throwError) */ - function startSession($pkg, $contents) + public function startSession($pkg, $contents) { if ($this->installphase != PEAR_TASK_INSTALL) { return false; @@ -284,40 +304,46 @@ function startSession($pkg, $contents) // remove the tasks: namespace if present $this->_pkg = $pkg; $this->_stripNamespace(); - $this->logger->log(0, 'Including external post-installation script "' . - $contents . '" - any errors are in this script'); + $this->logger->log( + 0, 'Including external post-installation script "'. + $contents.'" - any errors are in this script' + ); include_once $contents; if (class_exists($this->_class)) { $this->logger->log(0, 'Inclusion succeeded'); } else { - return $this->throwError('init of post-install script class "' . $this->_class - . '" failed'); + return $this->throwError( + 'init of post-install script class "'.$this->_class + .'" failed' + ); } - $this->_obj = new $this->_class; - $this->logger->log(1, 'running post-install script "' . $this->_class . '->init()"'); + $this->_obj = new $this->_class(); + $this->logger->log(1, 'running post-install script "'.$this->_class.'->init()"'); PEAR::pushErrorHandling(PEAR_ERROR_RETURN); $res = $this->_obj->init($this->config, $pkg, $this->_lastversion); PEAR::popErrorHandling(); if ($res) { $this->logger->log(0, 'init succeeded'); } else { - return $this->throwError('init of post-install script "' . $this->_class . - '->init()" failed'); + return $this->throwError( + 'init of post-install script "'.$this->_class. + '->init()" failed' + ); } $this->_contents = $contents; + return true; } /** * No longer used - * @see PEAR_PackageFile_v2::runPostinstallScripts() - * @param array an array of tasks - * @param string install or upgrade + * + * @see PEAR_PackageFile_v2::runPostinstallScripts() + * @param array an array of tasks + * @param string install or upgrade * @access protected - * @static */ - function run() + public static function run() { } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/pear/PEAR/Task/Postinstallscript/rw.php b/WEB-INF/lib/pear/PEAR/Task/Postinstallscript/rw.php index 8f358bf22..662960062 100644 --- a/WEB-INF/lib/pear/PEAR/Task/Postinstallscript/rw.php +++ b/WEB-INF/lib/pear/PEAR/Task/Postinstallscript/rw.php @@ -4,14 +4,13 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $ - * @link http://pear.php.net/package/PEAR - * @since File available since Release 1.4.0a10 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 */ /** * Base class @@ -24,7 +23,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a10 */ @@ -35,30 +34,31 @@ class PEAR_Task_Postinstallscript_rw extends PEAR_Task_Postinstallscript * * @var PEAR_PackageFile_v2_rw */ - var $_pkg; + public $_pkg; /** * Enter description here... * - * @param PEAR_PackageFile_v2_rw $pkg - * @param PEAR_Config $config - * @param PEAR_Frontend $logger - * @param array $fileXml + * @param PEAR_PackageFile_v2_rw $pkg Package + * @param PEAR_Config $config Config + * @param PEAR_Frontend $logger Logger + * @param array $fileXml XML + * * @return PEAR_Task_Postinstallscript_rw */ - function PEAR_Task_Postinstallscript_rw(&$pkg, &$config, &$logger, $fileXml) + function __construct(&$pkg, &$config, &$logger, $fileXml) { - parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + parent::__construct($config, $logger, PEAR_TASK_PACKAGE); $this->_contents = $fileXml; $this->_pkg = &$pkg; $this->_params = array(); } - function validate() + public function validate() { return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); } - function getName() + public function getName() { return 'postinstallscript'; } @@ -73,30 +73,31 @@ function getName() * * Use {@link addConditionTypeGroup()} to add a containing * a tag - * @param string $id id as seen by the script - * @param array|false $params array of getParam() calls, or false for no params + * + * @param string $id id as seen by the script + * @param array|false $params array of getParam() calls, or false for no params * @param string|false $instructions */ - function addParamGroup($id, $params = false, $instructions = false) + public function addParamGroup($id, $params = false, $instructions = false) { if ($params && isset($params[0]) && !isset($params[1])) { $params = $params[0]; } $stuff = array( - $this->_pkg->getTasksNs() . ':id' => $id, + $this->_pkg->getTasksNs().':id' => $id, ); if ($instructions) { - $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + $stuff[$this->_pkg->getTasksNs().':instructions'] = $instructions; } if ($params) { - $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + $stuff[$this->_pkg->getTasksNs().':param'] = $params; } - $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + $this->_params[$this->_pkg->getTasksNs().':paramgroup'][] = $stuff; } /** - * add a complex to the post-install script with conditions + * Add a complex to the post-install script with conditions * * This inserts a with * @@ -107,63 +108,75 @@ function addParamGroup($id, $params = false, $instructions = false) * * Use {@link addParamGroup()} to add a simple * - * @param string $id id as seen by the script - * @param string $oldgroup id of the section referenced by - * - * @param string $param name of the from the older section referenced - * by - * @param string $value value to match of the parameter - * @param string $conditiontype one of '=', '!=', 'preg_match' - * @param array|false $params array of getParam() calls, or false for no params + * @param string $id id as seen by the script + * @param string $oldgroup id of the section referenced by + * + * @param string $param name of the from the older section referenced + * by + * @param string $value value to match of the parameter + * @param string $conditiontype one of '=', '!=', 'preg_match' + * @param array|false $params array of getParam() calls, or false for no params * @param string|false $instructions */ - function addConditionTypeGroup($id, $oldgroup, $param, $value, $conditiontype = '=', - $params = false, $instructions = false) - { + public function addConditionTypeGroup($id, + $oldgroup, + $param, + $value, + $conditiontype = '=', + $params = false, + $instructions = false + ) { if ($params && isset($params[0]) && !isset($params[1])) { $params = $params[0]; } $stuff = array( - $this->_pkg->getTasksNs() . ':id' => $id, + $this->_pkg->getTasksNs().':id' => $id, ); if ($instructions) { - $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions; + $stuff[$this->_pkg->getTasksNs().':instructions'] = $instructions; } - $stuff[$this->_pkg->getTasksNs() . ':name'] = $oldgroup . '::' . $param; - $stuff[$this->_pkg->getTasksNs() . ':conditiontype'] = $conditiontype; - $stuff[$this->_pkg->getTasksNs() . ':value'] = $value; + $stuff[$this->_pkg->getTasksNs().':name'] = $oldgroup.'::'.$param; + $stuff[$this->_pkg->getTasksNs().':conditiontype'] = $conditiontype; + $stuff[$this->_pkg->getTasksNs().':value'] = $value; if ($params) { - $stuff[$this->_pkg->getTasksNs() . ':param'] = $params; + $stuff[$this->_pkg->getTasksNs().':param'] = $params; } - $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff; + $this->_params[$this->_pkg->getTasksNs().':paramgroup'][] = $stuff; } - function getXml() + public function getXml() { return $this->_params; } /** * Use to set up a param tag for use in creating a paramgroup - * @static + * + * @param mixed $name Name of parameter + * @param mixed $prompt Prompt + * @param string $type Type, defaults to 'string' + * @param mixed $default Default value + * + * @return array */ - function getParam($name, $prompt, $type = 'string', $default = null) - { + public static function getParam( + $name, $prompt, $type = 'string', $default = null + ) { if ($default !== null) { return array( - $this->_pkg->getTasksNs() . ':name' => $name, - $this->_pkg->getTasksNs() . ':prompt' => $prompt, - $this->_pkg->getTasksNs() . ':type' => $type, - $this->_pkg->getTasksNs() . ':default' => $default + $this->_pkg->getTasksNs().':name' => $name, + $this->_pkg->getTasksNs().':prompt' => $prompt, + $this->_pkg->getTasksNs().':type' => $type, + $this->_pkg->getTasksNs().':default' => $default, ); } + return array( - $this->_pkg->getTasksNs() . ':name' => $name, - $this->_pkg->getTasksNs() . ':prompt' => $prompt, - $this->_pkg->getTasksNs() . ':type' => $type, + $this->_pkg->getTasksNs().':name' => $name, + $this->_pkg->getTasksNs().':prompt' => $prompt, + $this->_pkg->getTasksNs().':type' => $type, ); } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/pear/PEAR/Task/Replace.php b/WEB-INF/lib/pear/PEAR/Task/Replace.php index 376df64df..7483282bb 100644 --- a/WEB-INF/lib/pear/PEAR/Task/Replace.php +++ b/WEB-INF/lib/pear/PEAR/Task/Replace.php @@ -4,14 +4,13 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Replace.php 313023 2011-07-06 19:17:11Z dufuz $ - * @link http://pear.php.net/package/PEAR - * @since File available since Release 1.4.0a1 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 */ /** * Base class @@ -24,24 +23,24 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ class PEAR_Task_Replace extends PEAR_Task_Common { - var $type = 'simple'; - var $phase = PEAR_TASK_PACKAGEANDINSTALL; - var $_replacements; + public $type = 'simple'; + public $phase = PEAR_TASK_PACKAGEANDINSTALL; + public $_replacements; /** * Validate the raw xml at parsing-time. - * @param PEAR_PackageFile_v2 - * @param array raw, parsed xml - * @param PEAR_Config - * @static + * + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config */ - function validateXml($pkg, $xml, $config, $fileXml) + public static function validateXml($pkg, $xml, $config, $fileXml) { if (!isset($xml['attribs'])) { return array(PEAR_TASK_ERROR_NOATTRIBS); @@ -58,33 +57,36 @@ function validateXml($pkg, $xml, $config, $fileXml) if ($xml['attribs']['type'] == 'pear-config') { if (!in_array($xml['attribs']['to'], $config->getKeys())) { return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], - $config->getKeys()); + $config->getKeys(), ); } } elseif ($xml['attribs']['type'] == 'php-const') { if (defined($xml['attribs']['to'])) { return true; } else { return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], - array('valid PHP constant')); + array('valid PHP constant'), ); } } elseif ($xml['attribs']['type'] == 'package-info') { - if (in_array($xml['attribs']['to'], + if (in_array( + $xml['attribs']['to'], array('name', 'summary', 'channel', 'notes', 'extends', 'description', 'release_notes', 'license', 'release-license', 'license-uri', 'version', 'api-version', 'state', 'api-state', 'release_date', - 'date', 'time'))) { + 'date', 'time', ) + )) { return true; } else { return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'], array('name', 'summary', 'channel', 'notes', 'extends', 'description', 'release_notes', 'license', 'release-license', 'license-uri', 'version', 'api-version', 'state', 'api-state', 'release_date', - 'date', 'time')); + 'date', 'time', ), ); } } else { return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'type', $xml['attribs']['type'], - array('pear-config', 'package-info', 'php-const')); + array('pear-config', 'package-info', 'php-const'), ); } + return true; } @@ -92,8 +94,9 @@ function validateXml($pkg, $xml, $config, $fileXml) * Initialize a task instance with the parameters * @param array raw, parsed xml * @param unused + * @param unused */ - function init($xml, $attribs) + public function init($xml, $attribs, $lastVersion = null) { $this->_replacements = isset($xml['attribs']) ? array($xml) : $xml; } @@ -102,13 +105,14 @@ function init($xml, $attribs) * Do a package.xml 1.0 replacement, with additional package-info fields available * * See validateXml() source for the complete list of allowed fields - * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 - * @param string file contents - * @param string the eventual final file location (informational only) + * + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail - * (use $this->throwError), otherwise return the new contents + * (use $this->throwError), otherwise return the new contents */ - function startSession($pkg, $contents, $dest) + public function startSession($pkg, $contents, $dest) { $subst_from = $subst_to = array(); foreach ($this->_replacements as $a) { @@ -124,6 +128,7 @@ function startSession($pkg, $contents, $dest) $to = $chan->getServer(); } else { $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; } } else { @@ -140,6 +145,7 @@ function startSession($pkg, $contents, $dest) } if (is_null($to)) { $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]"); + return false; } } elseif ($a['type'] == 'php-const') { @@ -150,6 +156,7 @@ function startSession($pkg, $contents, $dest) $to = constant($a['to']); } else { $this->logger->log(0, "$dest: invalid php-const replacement: $a[to]"); + return false; } } else { @@ -157,6 +164,7 @@ function startSession($pkg, $contents, $dest) $to = $t; } else { $this->logger->log(0, "$dest: invalid package-info replacement: $a[to]"); + return false; } } @@ -165,12 +173,14 @@ function startSession($pkg, $contents, $dest) $subst_to[] = $to; } } - $this->logger->log(3, "doing " . sizeof($subst_from) . - " substitution(s) for $dest"); + $this->logger->log( + 3, "doing ".sizeof($subst_from). + " substitution(s) for $dest" + ); if (sizeof($subst_from)) { $contents = str_replace($subst_from, $subst_to, $contents); } + return $contents; } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/pear/PEAR/Task/Replace/rw.php b/WEB-INF/lib/pear/PEAR/Task/Replace/rw.php index 32dad5862..ace1e9ea2 100644 --- a/WEB-INF/lib/pear/PEAR/Task/Replace/rw.php +++ b/WEB-INF/lib/pear/PEAR/Task/Replace/rw.php @@ -4,14 +4,13 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $ - * @link http://pear.php.net/package/PEAR - * @since File available since Release 1.4.0a10 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 */ /** * Base class @@ -24,38 +23,37 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a10 */ class PEAR_Task_Replace_rw extends PEAR_Task_Replace { - function PEAR_Task_Replace_rw(&$pkg, &$config, &$logger, $fileXml) + public function __construct(&$pkg, &$config, &$logger, $fileXml) { - parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + parent::__construct($config, $logger, PEAR_TASK_PACKAGE); $this->_contents = $fileXml; $this->_pkg = &$pkg; $this->_params = array(); } - function validate() + public function validate() { return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents); } - function setInfo($from, $to, $type) + public function setInfo($from, $to, $type) { $this->_params = array('attribs' => array('from' => $from, 'to' => $to, 'type' => $type)); } - function getName() + public function getName() { return 'replace'; } - function getXml() + public function getXml() { return $this->_params; } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/pear/PEAR/Task/Unixeol.php b/WEB-INF/lib/pear/PEAR/Task/Unixeol.php index 89ca81be3..6ef7174bb 100644 --- a/WEB-INF/lib/pear/PEAR/Task/Unixeol.php +++ b/WEB-INF/lib/pear/PEAR/Task/Unixeol.php @@ -4,14 +4,13 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Unixeol.php 313023 2011-07-06 19:17:11Z dufuz $ - * @link http://pear.php.net/package/PEAR - * @since File available since Release 1.4.0a1 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 */ /** * Base class @@ -24,28 +23,29 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ class PEAR_Task_Unixeol extends PEAR_Task_Common { - var $type = 'simple'; - var $phase = PEAR_TASK_PACKAGE; - var $_replacements; + public $type = 'simple'; + public $phase = PEAR_TASK_PACKAGE; + public $_replacements; /** * Validate the raw xml at parsing-time. - * @param PEAR_PackageFile_v2 - * @param array raw, parsed xml - * @param PEAR_Config - * @static + * + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config */ - function validateXml($pkg, $xml, $config, $fileXml) + public static function validateXml($pkg, $xml, $config, $fileXml) { if ($xml != '') { return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); } + return true; } @@ -53,8 +53,9 @@ function validateXml($pkg, $xml, $config, $fileXml) * Initialize a task instance with the parameters * @param array raw, parsed xml * @param unused + * @param unused */ - function init($xml, $attribs) + public function init($xml, $attribs, $lastVersion = null) { } @@ -62,16 +63,17 @@ function init($xml, $attribs) * Replace all line endings with line endings customized for the current OS * * See validateXml() source for the complete list of allowed fields - * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 - * @param string file contents - * @param string the eventual final file location (informational only) + * + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail - * (use $this->throwError), otherwise return the new contents + * (use $this->throwError), otherwise return the new contents */ - function startSession($pkg, $contents, $dest) + public function startSession($pkg, $contents, $dest) { $this->logger->log(3, "replacing all line endings with \\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\n", $contents); } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/pear/PEAR/Task/Unixeol/rw.php b/WEB-INF/lib/pear/PEAR/Task/Unixeol/rw.php index b2ae5fa5c..9134e2c93 100644 --- a/WEB-INF/lib/pear/PEAR/Task/Unixeol/rw.php +++ b/WEB-INF/lib/pear/PEAR/Task/Unixeol/rw.php @@ -4,14 +4,13 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $ - * @link http://pear.php.net/package/PEAR - * @since File available since Release 1.4.0a10 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 */ /** * Base class @@ -24,33 +23,33 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a10 */ class PEAR_Task_Unixeol_rw extends PEAR_Task_Unixeol { - function PEAR_Task_Unixeol_rw(&$pkg, &$config, &$logger, $fileXml) + function __construct(&$pkg, &$config, &$logger, $fileXml) { - parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + parent::__construct($config, $logger, PEAR_TASK_PACKAGE); $this->_contents = $fileXml; $this->_pkg = &$pkg; $this->_params = array(); } - function validate() + public function validate() { return true; } - function getName() + public function getName() { return 'unixeol'; } - function getXml() + public function getXml() { return ''; } } -?> \ No newline at end of file +?> diff --git a/WEB-INF/lib/pear/PEAR/Task/Windowseol.php b/WEB-INF/lib/pear/PEAR/Task/Windowseol.php index 8ba417115..620c940fe 100644 --- a/WEB-INF/lib/pear/PEAR/Task/Windowseol.php +++ b/WEB-INF/lib/pear/PEAR/Task/Windowseol.php @@ -4,14 +4,13 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Windowseol.php 313023 2011-07-06 19:17:11Z dufuz $ - * @link http://pear.php.net/package/PEAR - * @since File available since Release 1.4.0a1 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a1 */ /** * Base class @@ -19,33 +18,35 @@ require_once 'PEAR/Task/Common.php'; /** * Implements the windows line endsings file task. - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 - * @link http://pear.php.net/package/PEAR - * @since Class available since Release 1.4.0a1 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.10.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a1 */ class PEAR_Task_Windowseol extends PEAR_Task_Common { - var $type = 'simple'; - var $phase = PEAR_TASK_PACKAGE; - var $_replacements; + public $type = 'simple'; + public $phase = PEAR_TASK_PACKAGE; + public $_replacements; /** * Validate the raw xml at parsing-time. - * @param PEAR_PackageFile_v2 - * @param array raw, parsed xml - * @param PEAR_Config - * @static + * + * @param PEAR_PackageFile_v2 + * @param array raw, parsed xml + * @param PEAR_Config */ - function validateXml($pkg, $xml, $config, $fileXml) + public static function validateXml($pkg, $xml, $config, $fileXml) { if ($xml != '') { return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed'); } + return true; } @@ -53,8 +54,9 @@ function validateXml($pkg, $xml, $config, $fileXml) * Initialize a task instance with the parameters * @param array raw, parsed xml * @param unused + * @param unused */ - function init($xml, $attribs) + public function init($xml, $attribs, $lastVersion = null) { } @@ -62,16 +64,17 @@ function init($xml, $attribs) * Replace all line endings with windows line endings * * See validateXml() source for the complete list of allowed fields - * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 - * @param string file contents - * @param string the eventual final file location (informational only) + * + * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 + * @param string file contents + * @param string the eventual final file location (informational only) * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail - * (use $this->throwError), otherwise return the new contents + * (use $this->throwError), otherwise return the new contents */ - function startSession($pkg, $contents, $dest) + public function startSession($pkg, $contents, $dest) { $this->logger->log(3, "replacing all line endings with \\r\\n in $dest"); + return preg_replace("/\r\n|\n\r|\r|\n/", "\r\n", $contents); } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/pear/PEAR/Task/Windowseol/rw.php b/WEB-INF/lib/pear/PEAR/Task/Windowseol/rw.php index f0f1149c8..e3cf0052b 100644 --- a/WEB-INF/lib/pear/PEAR/Task/Windowseol/rw.php +++ b/WEB-INF/lib/pear/PEAR/Task/Windowseol/rw.php @@ -4,14 +4,13 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: rw.php 313023 2011-07-06 19:17:11Z dufuz $ - * @link http://pear.php.net/package/PEAR - * @since File available since Release 1.4.0a10 + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since Release 1.4.0a10 */ /** * Base class @@ -19,38 +18,39 @@ require_once 'PEAR/Task/Windowseol.php'; /** * Abstracts the windowseol task xml. - * @category pear - * @package PEAR - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 - * @link http://pear.php.net/package/PEAR - * @since Class available since Release 1.4.0a10 + * + * @category pear + * @package PEAR + * @author Greg Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 1.10.1 + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 1.4.0a10 */ class PEAR_Task_Windowseol_rw extends PEAR_Task_Windowseol { - function PEAR_Task_Windowseol_rw(&$pkg, &$config, &$logger, $fileXml) + function __construct(&$pkg, &$config, &$logger, $fileXml) { - parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE); + parent::__construct($config, $logger, PEAR_TASK_PACKAGE); $this->_contents = $fileXml; $this->_pkg = &$pkg; $this->_params = array(); } - function validate() + public function validate() { return true; } - function getName() + public function getName() { return 'windowseol'; } - function getXml() + public function getXml() { return ''; } } -?> \ No newline at end of file +?> diff --git a/WEB-INF/lib/pear/PEAR/Validate.php b/WEB-INF/lib/pear/PEAR/Validate.php index 176560bc2..8e29b7cd2 100644 --- a/WEB-INF/lib/pear/PEAR/Validate.php +++ b/WEB-INF/lib/pear/PEAR/Validate.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Validate.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -32,7 +31,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -85,9 +84,8 @@ function validPackageName($name, $validatepackagename = false) * to the PEAR naming convention, so the method is final and static. * @param string * @final - * @static */ - function validGroupName($name) + public static function validGroupName($name) { return (bool) preg_match('/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/', $name); } @@ -96,10 +94,9 @@ function validGroupName($name) * Determine whether $state represents a valid stability level * @param string * @return bool - * @static * @final */ - function validState($state) + public static function validState($state) { return in_array($state, array('snapshot', 'devel', 'alpha', 'beta', 'stable')); } @@ -107,10 +104,9 @@ function validState($state) /** * Get a list of valid stability levels * @return array - * @static * @final */ - function getValidStates() + public static function getValidStates() { return array('snapshot', 'devel', 'alpha', 'beta', 'stable'); } @@ -120,10 +116,9 @@ function getValidStates() * by version_compare * @param string * @return bool - * @static * @final */ - function validVersion($ver) + public static function validVersion($ver) { return (bool) preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); } @@ -207,7 +202,8 @@ function validatePackageName() $this->_packagexml->getExtends()) { $version = $this->_packagexml->getVersion() . ''; $name = $this->_packagexml->getPackage(); - $test = array_shift($a = explode('.', $version)); + $a = explode('.', $version); + $test = array_shift($a); if ($test == '0') { return true; } diff --git a/WEB-INF/lib/pear/PEAR/Validator/PECL.php b/WEB-INF/lib/pear/PEAR/Validator/PECL.php index 89b951f7b..830c8e9b2 100644 --- a/WEB-INF/lib/pear/PEAR/Validator/PECL.php +++ b/WEB-INF/lib/pear/PEAR/Validator/PECL.php @@ -9,7 +9,6 @@ * @author Greg Beaver * @copyright 1997-2006 The PHP Group * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: PECL.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a5 */ @@ -24,7 +23,7 @@ * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a5 */ diff --git a/WEB-INF/lib/pear/PEAR/XMLParser.php b/WEB-INF/lib/pear/PEAR/XMLParser.php index 7f091c16f..619743bcd 100644 --- a/WEB-INF/lib/pear/PEAR/XMLParser.php +++ b/WEB-INF/lib/pear/PEAR/XMLParser.php @@ -10,7 +10,6 @@ * @author Stephan Schmidt (original XML_Unserializer code) * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license New BSD License - * @version CVS: $Id: XMLParser.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a1 */ @@ -23,7 +22,7 @@ * @author Stephan Schmidt (original XML_Unserializer code) * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license New BSD License - * @version Release: 1.9.4 + * @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a1 */ @@ -95,11 +94,6 @@ function parse($data) $this->encoding = 'UTF-8'; } - if (version_compare(phpversion(), '5.0.0', 'lt') && $this->encoding == 'UTF-8') { - $data = utf8_decode($data); - $this->encoding = 'ISO-8859-1'; - } - $xp = xml_parser_create($this->encoding); xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, 0); xml_set_object($xp, $this); diff --git a/WEB-INF/lib/pear/PEAR5.php b/WEB-INF/lib/pear/PEAR5.php deleted file mode 100644 index 428606780..000000000 --- a/WEB-INF/lib/pear/PEAR5.php +++ /dev/null @@ -1,33 +0,0 @@ - * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: System.php 313024 2011-07-06 19:51:24Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 0.1 */ @@ -51,7 +50,7 @@ * @author Tomas V.V. Cox * @copyright 1997-2006 The PHP Group * @license http://opensource.org/licenses/bsd-license.php New BSD License -* @version Release: 1.9.4 +* @version Release: 1.10.1 * @link http://pear.php.net/package/PEAR * @since Class available since Release 0.1 * @static @@ -65,12 +64,27 @@ class System * @param string $short_options the allowed option short-tags * @param string $long_options the allowed option long-tags * @return array the given options and there values - * @static - * @access private */ - function _parseArgs($argv, $short_options, $long_options = null) + public static function _parseArgs($argv, $short_options, $long_options = null) { if (!is_array($argv) && $argv !== null) { + /* + // Quote all items that are a short option + $av = preg_split('/(\A| )--?[a-z0-9]+[ =]?((?getMessage(); @@ -124,10 +137,8 @@ function raiseError($error) * @param integer $aktinst starting deep of the lookup * @param bool $silent if true, do not emit errors. * @return array the structure of the dir - * @static - * @access private */ - function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false) + protected static function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false) { $struct = array('dirs' => array(), 'files' => array()); if (($dir = @opendir($sPath)) === false) { @@ -170,7 +181,7 @@ function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false) * @static * @see System::_dirToStruct() */ - function _multipleToStruct($files) + protected static function _multipleToStruct($files) { $struct = array('dirs' => array(), 'files' => array()); settype($files, 'array'); @@ -196,7 +207,7 @@ function _multipleToStruct($files) * @static * @access public */ - function rm($args) + public static function rm($args) { $opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-) if (PEAR::isError($opts)) { @@ -239,10 +250,8 @@ function rm($args) * The -p option will create parent directories * @param string $args the name of the director(y|ies) to create * @return bool True for success - * @static - * @access public */ - function mkDir($args) + public static function mkDir($args) { $opts = System::_parseArgs($args, 'pm:'); if (PEAR::isError($opts)) { @@ -310,10 +319,8 @@ function mkDir($args) * * @param string $args the arguments * @return boolean true on success - * @static - * @access public */ - function &cat($args) + public static function &cat($args) { $ret = null; $files = array(); @@ -384,10 +391,8 @@ function &cat($args) * @param string $args The arguments * @return mixed the full path of the created (file|dir) or false * @see System::tmpdir() - * @static - * @access public */ - function mktemp($args = null) + public static function mktemp($args = null) { static $first_time = true; $opts = System::_parseArgs($args, 't:d'); @@ -436,11 +441,8 @@ function mktemp($args = null) /** * Remove temporary files created my mkTemp. This function is executed * at script shutdown time - * - * @static - * @access private */ - function _removeTmpFiles() + public static function _removeTmpFiles() { if (count($GLOBALS['_System_temp_files'])) { $delete = $GLOBALS['_System_temp_files']; @@ -456,10 +458,9 @@ function _removeTmpFiles() * Note: php.ini-recommended removes the "E" from the variables_order setting, * making unavaible the $_ENV array, that s why we do tests with _ENV * - * @static * @return string The temporary directory on the system */ - function tmpdir() + public static function tmpdir() { if (OS_WINDOWS) { if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) { @@ -489,10 +490,9 @@ function tmpdir() * @param mixed $fallback Value to return if $program is not found * * @return mixed A string with the full path or false if not found - * @static * @author Stig Bakken */ - function which($program, $fallback = false) + public static function which($program, $fallback = false) { // enforce API if (!is_string($program) || '' == $program) { @@ -504,13 +504,11 @@ function which($program, $fallback = false) $path_elements[] = dirname($program); $program = basename($program); } else { - // Honor safe mode - if (!ini_get('safe_mode') || !$path = ini_get('safe_mode_exec_dir')) { - $path = getenv('PATH'); - if (!$path) { - $path = getenv('Path'); // some OSes are just stupid enough to do this - } + $path = getenv('PATH'); + if (!$path) { + $path = getenv('Path'); // some OSes are just stupid enough to do this } + $path_elements = explode(PATH_SEPARATOR, $path); } @@ -522,17 +520,14 @@ function which($program, $fallback = false) if (strpos($program, '.') !== false) { array_unshift($exe_suffixes, ''); } - // is_executable() is not available on windows for PHP4 - $pear_is_executable = (function_exists('is_executable')) ? 'is_executable' : 'is_file'; } else { $exe_suffixes = array(''); - $pear_is_executable = 'is_executable'; } foreach ($exe_suffixes as $suff) { foreach ($path_elements as $dir) { $file = $dir . DIRECTORY_SEPARATOR . $program . $suff; - if (@$pear_is_executable($file)) { + if (is_executable($file)) { return $file; } } @@ -561,10 +556,8 @@ function which($program, $fallback = false) * * @param mixed Either array or string with the command line * @return array Array of found files - * @static - * */ - function find($args) + public static function find($args) { if (!is_array($args)) { $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); diff --git a/WEB-INF/lib/pear/scripts/pear.bat b/WEB-INF/lib/pear/scripts/pear.bat index d7675bbfd..7f5837608 100644 --- a/WEB-INF/lib/pear/scripts/pear.bat +++ b/WEB-INF/lib/pear/scripts/pear.bat @@ -1,111 +1,111 @@ -@ECHO OFF - -REM ---------------------------------------------------------------------- -REM PHP version 5 -REM ---------------------------------------------------------------------- -REM Copyright (c) 1997-2010 The Authors -REM ---------------------------------------------------------------------- -REM http://opensource.org/licenses/bsd-license.php New BSD License -REM ---------------------------------------------------------------------- -REM Authors: Alexander Merz (alexmerz@php.net) -REM ---------------------------------------------------------------------- -REM -REM Last updated 12/29/2004 ($Id$ is not replaced if the file is binary) - -REM change this lines to match the paths of your system -REM ------------------- - - -REM Test to see if this is a raw pear.bat (uninstalled version) -SET TMPTMPTMPTMPT=@includ -SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@ -FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED) - -REM Check PEAR global ENV, set them if they do not exist -IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@" -IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@" -IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@" - -GOTO :INSTALLED - -:NOTINSTALLED -ECHO WARNING: This is a raw, uninstalled pear.bat - -REM Check to see if we can grab the directory of this file (Windows NT+) -IF %~n0 == pear ( -FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" ( -SET "PHP_PEAR_PHP_BIN=%%~$PATH:x" -echo Using PHP Executable "%PHP_PEAR_PHP_BIN%" -"%PHP_PEAR_PHP_BIN%" -v -GOTO :NEXTTEST -)) -GOTO :FAILAUTODETECT -:NEXTTEST -IF "%PHP_PEAR_PHP_BIN%" NEQ "" ( - -REM We can use this PHP to run a temporary php file to get the dirname of pear - -echo ^ > ~~getloc.php -"%PHP_PEAR_PHP_BIN%" ~~getloc.php -set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a -DEL ~a.a -DEL ~~getloc.php -set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear" - -REM Make sure there is a pearcmd.php at our disposal - -IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php ( -IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php -IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php -IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php -) -) -GOTO :INSTALLED -) ELSE ( -REM Windows Me/98 cannot succeed, so allow the batch to fail -) -:FAILAUTODETECT -echo WARNING: failed to auto-detect pear information -:INSTALLED - -REM Check Folders and files -IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR -IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2 -IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR -IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR - -REM launch pearcmd -GOTO RUN -:PEAR_INSTALL_ERROR -ECHO PHP_PEAR_INSTALL_DIR is not set correctly. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_INSTALL_DIR% -GOTO END -:PEAR_INSTALL_ERROR2 -ECHO PHP_PEAR_INSTALL_DIR is not set correctly. -ECHO pearcmd.php could not be found there. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_INSTALL_DIR% -GOTO END -:PEAR_BIN_ERROR -ECHO PHP_PEAR_BIN_DIR is not set correctly. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_BIN_DIR% -GOTO END -:PEAR_PHPBIN_ERROR -ECHO PHP_PEAR_PHP_BIN is not set correctly. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_PHP_BIN% -GOTO END -:RUN -"%PHP_PEAR_PHP_BIN%" -C -d date.timezone=UTC -d output_buffering=1 -d safe_mode=0 -d open_basedir="" -d auto_prepend_file="" -d auto_append_file="" -d variables_order=EGPCS -d register_argc_argv="On" -d "include_path='%PHP_PEAR_INSTALL_DIR%'" -f "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9 -:END +@ECHO OFF + +REM ---------------------------------------------------------------------- +REM PHP version 5 +REM ---------------------------------------------------------------------- +REM Copyright (c) 1997-2010 The Authors +REM ---------------------------------------------------------------------- +REM http://opensource.org/licenses/bsd-license.php New BSD License +REM ---------------------------------------------------------------------- +REM Authors: Alexander Merz (alexmerz@php.net) +REM ---------------------------------------------------------------------- +REM +REM Last updated 12/29/2004 ($Id$ is not replaced if the file is binary) + +REM change this lines to match the paths of your system +REM ------------------- + + +REM Test to see if this is a raw pear.bat (uninstalled version) +SET TMPTMPTMPTMPT=@includ +SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@ +FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED) + +REM Check PEAR global ENV, set them if they do not exist +IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@" +IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@" +IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@" + +GOTO :INSTALLED + +:NOTINSTALLED +ECHO WARNING: This is a raw, uninstalled pear.bat + +REM Check to see if we can grab the directory of this file (Windows NT+) +IF %~n0 == pear ( +FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" ( +SET "PHP_PEAR_PHP_BIN=%%~$PATH:x" +echo Using PHP Executable "%PHP_PEAR_PHP_BIN%" +"%PHP_PEAR_PHP_BIN%" -v +GOTO :NEXTTEST +)) +GOTO :FAILAUTODETECT +:NEXTTEST +IF "%PHP_PEAR_PHP_BIN%" NEQ "" ( + +REM We can use this PHP to run a temporary php file to get the dirname of pear + +echo ^ > ~~getloc.php +"%PHP_PEAR_PHP_BIN%" ~~getloc.php +set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a +DEL ~a.a +DEL ~~getloc.php +set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear" + +REM Make sure there is a pearcmd.php at our disposal + +IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php ( +IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +) +) +GOTO :INSTALLED +) ELSE ( +REM Windows Me/98 cannot succeed, so allow the batch to fail +) +:FAILAUTODETECT +echo WARNING: failed to auto-detect pear information +:INSTALLED + +REM Check Folders and files +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2 +IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR +IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR + +REM launch pearcmd +GOTO RUN +:PEAR_INSTALL_ERROR +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_INSTALL_ERROR2 +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO pearcmd.php could not be found there. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_BIN_ERROR +ECHO PHP_PEAR_BIN_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_BIN_DIR% +GOTO END +:PEAR_PHPBIN_ERROR +ECHO PHP_PEAR_PHP_BIN is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_PHP_BIN% +GOTO END +:RUN +"%PHP_PEAR_PHP_BIN%" -C -d date.timezone=UTC -d output_buffering=1 -d safe_mode=0 -d open_basedir="" -d auto_prepend_file="" -d auto_append_file="" -d variables_order=EGPCS -d register_argc_argv="On" -d "include_path='%PHP_PEAR_INSTALL_DIR%'" -f "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9 +:END @ECHO ON \ No newline at end of file diff --git a/WEB-INF/lib/pear/scripts/pearcmd.php b/WEB-INF/lib/pear/scripts/pearcmd.php index a3a928a3c..efc8a8dbb 100644 --- a/WEB-INF/lib/pear/scripts/pearcmd.php +++ b/WEB-INF/lib/pear/scripts/pearcmd.php @@ -6,17 +6,16 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Stig Bakken - * @author Tomas V.V.Cox - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: pearcmd.php 313023 2011-07-06 19:17:11Z dufuz $ - * @link http://pear.php.net/package/PEAR + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V.Cox + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR */ -ob_end_clean(); +@ob_end_clean(); if (!defined('PEAR_RUNTYPE')) { // this is defined in peclcmd.php as 'pecl' define('PEAR_RUNTYPE', 'pear'); @@ -25,21 +24,20 @@ /** * @nodep Gtk */ -if ('@include_path@' != '@'.'include_path'.'@') { - ini_set('include_path', '@include_path@'); +//the space is needed for windows include paths with trailing backslash +// http://pear.php.net/bugs/bug.php?id=19482 +if ('@include_path@ ' != '@'.'include_path'.'@ ') { + ini_set('include_path', trim('@include_path@ '). PATH_SEPARATOR . get_include_path()); $raw = false; } else { // this is a raw, uninstalled pear, either a cvs checkout, or php distro $raw = true; } @ini_set('allow_url_fopen', true); -if (!ini_get('safe_mode')) { - @set_time_limit(0); -} +@set_time_limit(0); ob_implicit_flush(true); @ini_set('track_errors', true); @ini_set('html_errors', false); -@ini_set('magic_quotes_runtime', false); $_PEAR_PHPDIR = '#$%^&*'; set_error_handler('error_handler'); @@ -57,7 +55,8 @@ // remove this next part when we stop supporting that crap-ass PHP 4.2 if (!isset($_SERVER['argv']) && !isset($argv) && !isset($HTTP_SERVER_VARS['argv'])) { - echo 'ERROR: either use the CLI php executable, or set register_argc_argv=On in php.ini'; + echo 'ERROR: either use the CLI php executable, ' . + 'or set register_argc_argv=On in php.ini'; exit(1); } @@ -78,18 +77,14 @@ $fetype = 'CLI'; if ($progname == 'gpear' || $progname == 'pear-gtk') { - $fetype = 'Gtk'; + $fetype = 'Gtk2'; } else { foreach ($opts as $opt) { if ($opt[0] == 'G') { - $fetype = 'Gtk'; + $fetype = 'Gtk2'; } } } -//Check if Gtk and PHP >= 5.1.0 -if ($fetype == 'Gtk' && version_compare(phpversion(), '5.1.0', '>=')) { - $fetype = 'Gtk2'; -} $pear_user_config = ''; $pear_system_config = ''; @@ -99,12 +94,12 @@ foreach ($opts as $opt) { switch ($opt[0]) { - case 'c': - $pear_user_config = $opt[1]; - break; - case 'C': - $pear_system_config = $opt[1]; - break; + case 'c': + $pear_user_config = $opt[1]; + break; + case 'C': + $pear_system_config = $opt[1]; + break; } } @@ -115,10 +110,10 @@ if (PEAR::isError($config)) { $_file = ''; if ($pear_user_config !== false) { - $_file .= $pear_user_config; + $_file .= $pear_user_config; } if ($pear_system_config !== false) { - $_file .= '/' . $pear_system_config; + $_file .= '/' . $pear_system_config; } if ($_file == '/') { $_file = 'The default config file'; @@ -133,11 +128,6 @@ $_PEAR_PHPDIR = $config->get('php_dir'); $ui->setConfig($config); PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array($ui, "displayFatalError")); -if (ini_get('safe_mode')) { - $ui->outputData('WARNING: running in safe mode requires that all files created ' . - 'be the same uid as the current script. PHP reports this script is uid: ' . - @getmyuid() . ', and current user is: ' . @get_current_user()); -} $verbose = $config->get("verbose"); $cmdopts = array(); @@ -147,17 +137,20 @@ $found = false; foreach ($opts as $opt) { if ($opt[0] == 'd' || $opt[0] == 'D') { - $found = true; // the user knows what they are doing, and are setting config values + // the user knows what they are doing, and are setting config values + $found = true; } } if (!$found) { // no prior runs, try to install PEAR - if (strpos(dirname(__FILE__), 'scripts')) { - $packagexml = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'package2.xml'; - $pearbase = dirname(dirname(__FILE__)); + $parent = dirname(__FILE__); + if (strpos($parent, 'scripts')) { + $grandparent = dirname($parent); + $packagexml = $grandparent . DIRECTORY_SEPARATOR . 'package2.xml'; + $pearbase = $grandparent; } else { - $packagexml = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'package2.xml'; - $pearbase = dirname(__FILE__); + $packagexml = $parent . DIRECTORY_SEPARATOR . 'package2.xml'; + $pearbase = $parent; } if (file_exists($packagexml)) { $options[1] = array( @@ -168,7 +161,10 @@ $config->set('data_dir', $pearbase . DIRECTORY_SEPARATOR . 'data'); $config->set('doc_dir', $pearbase . DIRECTORY_SEPARATOR . 'docs'); $config->set('test_dir', $pearbase . DIRECTORY_SEPARATOR . 'tests'); - $config->set('ext_dir', $pearbase . DIRECTORY_SEPARATOR . 'extensions'); + $config->set( + 'ext_dir', + $pearbase . DIRECTORY_SEPARATOR . 'extensions' + ); $config->set('bin_dir', $pearbase); $config->mergeConfigFile($pearbase . 'pear.ini', false); $config->store(); @@ -180,56 +176,64 @@ foreach ($opts as $opt) { $param = !empty($opt[1]) ? $opt[1] : true; switch ($opt[0]) { - case 'd': - if ($param === true) { - die('Invalid usage of "-d" option, expected -d config_value=value, ' . - 'received "-d"' . "\n"); - } - $possible = explode('=', $param); - if (count($possible) != 2) { - die('Invalid usage of "-d" option, expected -d config_value=value, received "' . - $param . '"' . "\n"); - } - list($key, $value) = explode('=', $param); - $config->set($key, $value, 'user'); - break; - case 'D': - if ($param === true) { - die('Invalid usage of "-d" option, expected -d config_value=value, ' . - 'received "-d"' . "\n"); - } - $possible = explode('=', $param); - if (count($possible) != 2) { - die('Invalid usage of "-d" option, expected -d config_value=value, received "' . - $param . '"' . "\n"); - } - list($key, $value) = explode('=', $param); - $config->set($key, $value, 'system'); - break; - case 's': - $store_user_config = true; - break; - case 'S': - $store_system_config = true; - break; - case 'u': - $config->remove($param, 'user'); - break; - case 'v': - $config->set('verbose', $config->get('verbose') + 1); - break; - case 'q': - $config->set('verbose', $config->get('verbose') - 1); - break; - case 'V': - usage(null, 'version'); - case 'c': - case 'C': - break; - default: - // all non pear params goes to the command - $cmdopts[$opt[0]] = $param; - break; + case 'd': + if ($param === true) { + die( + 'Invalid usage of "-d" option, expected -d config_value=value, ' . + 'received "-d"' . "\n" + ); + } + $possible = explode('=', $param); + if (count($possible) != 2) { + die( + 'Invalid usage of "-d" option, expected ' . + '-d config_value=value, received "' . $param . '"' . "\n" + ); + } + list($key, $value) = explode('=', $param); + $config->set($key, $value, 'user'); + break; + case 'D': + if ($param === true) { + die( + 'Invalid usage of "-d" option, expected ' . + '-d config_value=value, received "-d"' . "\n" + ); + } + $possible = explode('=', $param); + if (count($possible) != 2) { + die( + 'Invalid usage of "-d" option, expected ' . + '-d config_value=value, received "' . $param . '"' . "\n" + ); + } + list($key, $value) = explode('=', $param); + $config->set($key, $value, 'system'); + break; + case 's': + $store_user_config = true; + break; + case 'S': + $store_system_config = true; + break; + case 'u': + $config->remove($param, 'user'); + break; + case 'v': + $config->set('verbose', $config->get('verbose') + 1); + break; + case 'q': + $config->set('verbose', $config->get('verbose') - 1); + break; + case 'V': + usage(null, 'version'); + case 'c': + case 'C': + break; + default: + // all non pear params goes to the command + $cmdopts[$opt[0]] = $param; + break; } } @@ -246,75 +250,93 @@ exit; } -if ($fetype == 'Gtk' || $fetype == 'Gtk2') { +if ($fetype == 'Gtk2') { if (!$config->validConfiguration()) { - PEAR::raiseError('CRITICAL ERROR: no existing valid configuration files found in files ' . - "'$pear_user_config' or '$pear_system_config', please copy an existing configuration" . - 'file to one of these locations, or use the -c and -s options to create one'); + PEAR::raiseError( + "CRITICAL ERROR: no existing valid configuration files found in " . + "files '$pear_user_config' or '$pear_system_config', " . + "please copy an existing configuration file to one of these " . + "locations, or use the -c and -s options to create one" + ); } Gtk::main(); -} else do { - if ($command == 'help') { - usage(null, @$options[1][1]); - } - - if (!$config->validConfiguration()) { - PEAR::raiseError('CRITICAL ERROR: no existing valid configuration files found in files ' . - "'$pear_user_config' or '$pear_system_config', please copy an existing configuration" . - 'file to one of these locations, or use the -c and -s options to create one'); - } +} else { + do { + if ($command == 'help') { + usage(null, isset($options[1][1]) ? $options[1][1] : null); + } - PEAR::pushErrorHandling(PEAR_ERROR_RETURN); - $cmd = PEAR_Command::factory($command, $config); - PEAR::popErrorHandling(); - if (PEAR::isError($cmd)) { - usage(null, @$options[1][0]); - } + if (!$config->validConfiguration()) { + PEAR::raiseError( + "CRITICAL ERROR: no existing valid configuration files found " . + "in files '$pear_user_config' or '$pear_system_config', " . + "please copy an existing configuration file to one of " . + "these locations, or use the -c and -s options to create one" + ); + } - $short_args = $long_args = null; - PEAR_Command::getGetoptArgs($command, $short_args, $long_args); - array_shift($options[1]); - $tmp = Console_Getopt::getopt2($options[1], $short_args, $long_args); + PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $cmd = PEAR_Command::factory($command, $config); + PEAR::popErrorHandling(); + if (PEAR::isError($cmd)) { + usage(null, isset($options[1][0]) ? $options[1][0] : null); + } - if (PEAR::isError($tmp)) { - break; - } + $short_args = $long_args = null; + PEAR_Command::getGetoptArgs($command, $short_args, $long_args); + array_shift($options[1]); + $tmp = Console_Getopt::getopt2($options[1], $short_args, $long_args); - list($tmpopt, $params) = $tmp; - $opts = array(); - foreach ($tmpopt as $foo => $tmp2) { - list($opt, $value) = $tmp2; - if ($value === null) { - $value = true; // options without args + if (PEAR::isError($tmp)) { + break; } - if (strlen($opt) == 1) { - $cmdoptions = $cmd->getOptions($command); - foreach ($cmdoptions as $o => $d) { - if (isset($d['shortopt']) && $d['shortopt'] == $opt) { - $opts[$o] = $value; - } + list($tmpopt, $params) = $tmp; + $opts = array(); + foreach ($tmpopt as $foo => $tmp2) { + list($opt, $value) = $tmp2; + if ($value === null) { + $value = true; // options without args } - } else { - if (substr($opt, 0, 2) == '--') { - $opts[substr($opt, 2)] = $value; + + if (strlen($opt) == 1) { + $cmdoptions = $cmd->getOptions($command); + foreach ($cmdoptions as $o => $d) { + if (isset($d['shortopt']) && $d['shortopt'] == $opt) { + $opts[$o] = $value; + } + } + } else { + if (substr($opt, 0, 2) == '--') { + $opts[substr($opt, 2)] = $value; + } } } - } - $ok = $cmd->run($command, $opts, $params); - if ($ok === false) { - PEAR::raiseError("unknown command `$command'"); - } + $ok = $cmd->run($command, $opts, $params); + if ($ok === false) { + PEAR::raiseError("unknown command `$command'"); + } - if (PEAR::isError($ok)) { - PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array($ui, "displayFatalError")); - PEAR::raiseError($ok); - } -} while (false); + if (PEAR::isError($ok)) { + PEAR::setErrorHandling( + PEAR_ERROR_CALLBACK, array($ui, "displayFatalError") + ); + PEAR::raiseError($ok); + } + } while (false); +} // {{{ usage() +/** + * Display usage information + * + * @param mixed $error Optional error message + * @param mixed $helpsubject Optional subject/command to display help for + * + * @return void + */ function usage($error = null, $helpsubject = null) { global $progname, $all_commands; @@ -339,7 +361,10 @@ function usage($error = null, $helpsubject = null) "Usage: $progname [options] command [command-options] \n". "Type \"$progname help options\" to list all options.\n". "Type \"$progname help shortcuts\" to list all command shortcuts.\n". - "Type \"$progname help \" to get the help for the specified command."; + "Type \"$progname help version\" or ". + "\"$progname version\" to list version information.\n". + "Type \"$progname help \" to get the help ". + "for the specified command."; } fputs($stdout, "$put\n"); fclose($stdout); @@ -350,6 +375,13 @@ function usage($error = null, $helpsubject = null) exit(1); } +/** + * Return help string for specified command + * + * @param string $command Command to return help for + * + * @return void + */ function cmdHelp($command) { global $progname, $all_commands, $config; @@ -399,23 +431,40 @@ function cmdHelp($command) // }}} -function error_handler($errno, $errmsg, $file, $line, $vars) { - if ((defined('E_STRICT') && $errno & E_STRICT) || (defined('E_DEPRECATED') && - $errno & E_DEPRECATED) || !error_reporting()) { - if (defined('E_STRICT') && $errno & E_STRICT) { +/** + * error_handler + * + * @param mixed $errno Error number + * @param mixed $errmsg Message + * @param mixed $file Filename + * @param mixed $line Line number + * @param mixed $vars Variables + * + * @access public + * @return boolean + */ +function error_handler($errno, $errmsg, $file, $line, $vars) +{ + if ($errno & E_STRICT + || $errno & E_DEPRECATED + || !error_reporting() + ) { + if ($errno & E_STRICT) { return; // E_STRICT } - if (defined('E_DEPRECATED') && $errno & E_DEPRECATED) { + if ($errno & E_DEPRECATED) { return; // E_DEPRECATED } - if ($GLOBALS['config']->get('verbose') < 4) { + if (!error_reporting() && isset($GLOBALS['config']) && $GLOBALS['config']->get('verbose') < 4) { return false; // @silenced error, show all if debug is high enough } } $errortype = array ( + E_DEPRECATED => 'Deprecated Warning', E_ERROR => "Error", E_WARNING => "Warning", E_PARSE => "Parsing Error", + E_STRICT => 'Strict Warning', E_NOTICE => "Notice", E_CORE_ERROR => "Core Error", E_CORE_WARNING => "Core Warning", @@ -445,4 +494,4 @@ function error_handler($errno, $errmsg, $file, $line, $vars) { * mode: php * End: */ -// vim600:syn=php \ No newline at end of file +// vim600:syn=php diff --git a/WEB-INF/lib/pear/scripts/peardev.bat b/WEB-INF/lib/pear/scripts/peardev.bat index 8b67c7d9f..48e03872b 100644 --- a/WEB-INF/lib/pear/scripts/peardev.bat +++ b/WEB-INF/lib/pear/scripts/peardev.bat @@ -1,115 +1,115 @@ -@ECHO OFF - -REM ---------------------------------------------------------------------- -REM PHP version 5 -REM ---------------------------------------------------------------------- -REM Copyright (c) 1997-2004 The PHP Group -REM ---------------------------------------------------------------------- -REM This source file is subject to version 3.0 of the PHP license, -REM that is bundled with this package in the file LICENSE, and is -REM available at through the world-wide-web at -REM http://www.php.net/license/3_0.txt. -REM If you did not receive a copy of the PHP license and are unable to -REM obtain it through the world-wide-web, please send a note to -REM license@php.net so we can mail you a copy immediately. -REM ---------------------------------------------------------------------- -REM Authors: Alexander Merz (alexmerz@php.net) -REM ---------------------------------------------------------------------- -REM -REM $Id: peardev.bat,v 1.6 2007-09-03 03:00:17 cellog Exp $ - -REM change this lines to match the paths of your system -REM ------------------- - - -REM Test to see if this is a raw pear.bat (uninstalled version) -SET TMPTMPTMPTMPT=@includ -SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@ -FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED) - -REM Check PEAR global ENV, set them if they do not exist -IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@" -IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@" -IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@" -GOTO :INSTALLED - -:NOTINSTALLED -ECHO WARNING: This is a raw, uninstalled pear.bat - -REM Check to see if we can grab the directory of this file (Windows NT+) -IF %~n0 == pear ( -FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" ( -SET "PHP_PEAR_PHP_BIN=%%~$PATH:x" -echo Using PHP Executable "%PHP_PEAR_PHP_BIN%" -"%PHP_PEAR_PHP_BIN%" -v -GOTO :NEXTTEST -)) -GOTO :FAILAUTODETECT -:NEXTTEST -IF "%PHP_PEAR_PHP_BIN%" NEQ "" ( - -REM We can use this PHP to run a temporary php file to get the dirname of pear - -echo ^ > ~~getloc.php -"%PHP_PEAR_PHP_BIN%" ~~getloc.php -set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a -DEL ~a.a -DEL ~~getloc.php -set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear" - -REM Make sure there is a pearcmd.php at our disposal - -IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php ( -IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php -IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php -IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php -) -) -GOTO :INSTALLED -) ELSE ( -REM Windows Me/98 cannot succeed, so allow the batch to fail -) -:FAILAUTODETECT -echo WARNING: failed to auto-detect pear information -:INSTALLED - -REM Check Folders and files -IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR -IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2 -IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR -IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR -REM launch pearcmd -GOTO RUN -:PEAR_INSTALL_ERROR -ECHO PHP_PEAR_INSTALL_DIR is not set correctly. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_INSTALL_DIR% -GOTO END -:PEAR_INSTALL_ERROR2 -ECHO PHP_PEAR_INSTALL_DIR is not set correctly. -ECHO pearcmd.php could not be found there. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_INSTALL_DIR% -GOTO END -:PEAR_BIN_ERROR -ECHO PHP_PEAR_BIN_DIR is not set correctly. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_BIN_DIR% -GOTO END -:PEAR_PHPBIN_ERROR -ECHO PHP_PEAR_PHP_BIN is not set correctly. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_PHP_BIN% -GOTO END -:RUN -"%PHP_PEAR_PHP_BIN%" -C -d date.timezone=UTC -d memory_limit="-1" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d auto_append_file="" -d variables_order=EGPCS -d open_basedir="" -d output_buffering=1 -d "include_path='%PHP_PEAR_INSTALL_DIR%'" -f "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9 -:END +@ECHO OFF + +REM ---------------------------------------------------------------------- +REM PHP version 5 +REM ---------------------------------------------------------------------- +REM Copyright (c) 1997-2004 The PHP Group +REM ---------------------------------------------------------------------- +REM This source file is subject to version 3.0 of the PHP license, +REM that is bundled with this package in the file LICENSE, and is +REM available at through the world-wide-web at +REM http://www.php.net/license/3_0.txt. +REM If you did not receive a copy of the PHP license and are unable to +REM obtain it through the world-wide-web, please send a note to +REM license@php.net so we can mail you a copy immediately. +REM ---------------------------------------------------------------------- +REM Authors: Alexander Merz (alexmerz@php.net) +REM ---------------------------------------------------------------------- +REM +REM $Id: peardev.bat,v 1.6 2007-09-03 03:00:17 cellog Exp $ + +REM change this lines to match the paths of your system +REM ------------------- + + +REM Test to see if this is a raw pear.bat (uninstalled version) +SET TMPTMPTMPTMPT=@includ +SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@ +FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED) + +REM Check PEAR global ENV, set them if they do not exist +IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@" +IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@" +IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@" +GOTO :INSTALLED + +:NOTINSTALLED +ECHO WARNING: This is a raw, uninstalled pear.bat + +REM Check to see if we can grab the directory of this file (Windows NT+) +IF %~n0 == pear ( +FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" ( +SET "PHP_PEAR_PHP_BIN=%%~$PATH:x" +echo Using PHP Executable "%PHP_PEAR_PHP_BIN%" +"%PHP_PEAR_PHP_BIN%" -v +GOTO :NEXTTEST +)) +GOTO :FAILAUTODETECT +:NEXTTEST +IF "%PHP_PEAR_PHP_BIN%" NEQ "" ( + +REM We can use this PHP to run a temporary php file to get the dirname of pear + +echo ^ > ~~getloc.php +"%PHP_PEAR_PHP_BIN%" ~~getloc.php +set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a +DEL ~a.a +DEL ~~getloc.php +set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear" + +REM Make sure there is a pearcmd.php at our disposal + +IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php ( +IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +) +) +GOTO :INSTALLED +) ELSE ( +REM Windows Me/98 cannot succeed, so allow the batch to fail +) +:FAILAUTODETECT +echo WARNING: failed to auto-detect pear information +:INSTALLED + +REM Check Folders and files +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2 +IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR +IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR +REM launch pearcmd +GOTO RUN +:PEAR_INSTALL_ERROR +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_INSTALL_ERROR2 +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO pearcmd.php could not be found there. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_BIN_ERROR +ECHO PHP_PEAR_BIN_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_BIN_DIR% +GOTO END +:PEAR_PHPBIN_ERROR +ECHO PHP_PEAR_PHP_BIN is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_PHP_BIN% +GOTO END +:RUN +"%PHP_PEAR_PHP_BIN%" -C -d date.timezone=UTC -d memory_limit="-1" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d auto_append_file="" -d variables_order=EGPCS -d open_basedir="" -d output_buffering=1 -d "include_path='%PHP_PEAR_INSTALL_DIR%'" -f "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9 +:END @ECHO ON \ No newline at end of file diff --git a/WEB-INF/lib/pear/scripts/pecl.bat b/WEB-INF/lib/pear/scripts/pecl.bat index e7c823bc1..f604284de 100644 --- a/WEB-INF/lib/pear/scripts/pecl.bat +++ b/WEB-INF/lib/pear/scripts/pecl.bat @@ -1,115 +1,115 @@ -@ECHO OFF - -REM ---------------------------------------------------------------------- -REM PHP version 5 -REM ---------------------------------------------------------------------- -REM Copyright (c) 1997-2004 The PHP Group -REM ---------------------------------------------------------------------- -REM This source file is subject to version 3.0 of the PHP license, -REM that is bundled with this package in the file LICENSE, and is -REM available at through the world-wide-web at -REM http://www.php.net/license/3_0.txt. -REM If you did not receive a copy of the PHP license and are unable to -REM obtain it through the world-wide-web, please send a note to -REM license@php.net so we can mail you a copy immediately. -REM ---------------------------------------------------------------------- -REM Authors: Alexander Merz (alexmerz@php.net) -REM ---------------------------------------------------------------------- -REM -REM Last updated 02/08/2004 ($Id$ is not replaced if the file is binary) - -REM change this lines to match the paths of your system -REM ------------------- - - -REM Test to see if this is a raw pear.bat (uninstalled version) -SET TMPTMPTMPTMPT=@includ -SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@ -FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED) - -REM Check PEAR global ENV, set them if they do not exist -IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@" -IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@" -IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@" -GOTO :INSTALLED - -:NOTINSTALLED -ECHO WARNING: This is a raw, uninstalled pear.bat - -REM Check to see if we can grab the directory of this file (Windows NT+) -IF %~n0 == pear ( -FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" ( -SET "PHP_PEAR_PHP_BIN=%%~$PATH:x" -echo Using PHP Executable "%PHP_PEAR_PHP_BIN%" -"%PHP_PEAR_PHP_BIN%" -v -GOTO :NEXTTEST -)) -GOTO :FAILAUTODETECT -:NEXTTEST -IF "%PHP_PEAR_PHP_BIN%" NEQ "" ( - -REM We can use this PHP to run a temporary php file to get the dirname of pear - -echo ^ > ~~getloc.php -"%PHP_PEAR_PHP_BIN%" ~~getloc.php -set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a -DEL ~a.a -DEL ~~getloc.php -set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear" - -REM Make sure there is a pearcmd.php at our disposal - -IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php ( -IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php -IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php -IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php -) -) -GOTO :INSTALLED -) ELSE ( -REM Windows Me/98 cannot succeed, so allow the batch to fail -) -:FAILAUTODETECT -echo WARNING: failed to auto-detect pear information -:INSTALLED - -REM Check Folders and files -IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR -IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2 -IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR -IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR -REM launch pearcmd -GOTO RUN -:PEAR_INSTALL_ERROR -ECHO PHP_PEAR_INSTALL_DIR is not set correctly. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_INSTALL_DIR% -GOTO END -:PEAR_INSTALL_ERROR2 -ECHO PHP_PEAR_INSTALL_DIR is not set correctly. -ECHO pearcmd.php could not be found there. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_INSTALL_DIR% -GOTO END -:PEAR_BIN_ERROR -ECHO PHP_PEAR_BIN_DIR is not set correctly. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_BIN_DIR% -GOTO END -:PEAR_PHPBIN_ERROR -ECHO PHP_PEAR_PHP_BIN is not set correctly. -ECHO Please fix it using your environment variable or modify -ECHO the default value in pear.bat -ECHO The current value is: -ECHO %PHP_PEAR_PHP_BIN% -GOTO END -:RUN -"%PHP_PEAR_PHP_BIN%" -C -n -d date.timezone=UTC -d output_buffering=1 -d safe_mode=0 -d "include_path='%PHP_PEAR_INSTALL_DIR%'" -d register_argc_argv="On" -d variables_order=EGPCS -f "%PHP_PEAR_INSTALL_DIR%\peclcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9 -:END +@ECHO OFF + +REM ---------------------------------------------------------------------- +REM PHP version 5 +REM ---------------------------------------------------------------------- +REM Copyright (c) 1997-2004 The PHP Group +REM ---------------------------------------------------------------------- +REM This source file is subject to version 3.0 of the PHP license, +REM that is bundled with this package in the file LICENSE, and is +REM available at through the world-wide-web at +REM http://www.php.net/license/3_0.txt. +REM If you did not receive a copy of the PHP license and are unable to +REM obtain it through the world-wide-web, please send a note to +REM license@php.net so we can mail you a copy immediately. +REM ---------------------------------------------------------------------- +REM Authors: Alexander Merz (alexmerz@php.net) +REM ---------------------------------------------------------------------- +REM +REM Last updated 02/08/2004 ($Id$ is not replaced if the file is binary) + +REM change this lines to match the paths of your system +REM ------------------- + + +REM Test to see if this is a raw pear.bat (uninstalled version) +SET TMPTMPTMPTMPT=@includ +SET PMTPMTPMT=%TMPTMPTMPTMPT%e_path@ +FOR %%x IN ("@include_path@") DO (if %%x=="%PMTPMTPMT%" GOTO :NOTINSTALLED) + +REM Check PEAR global ENV, set them if they do not exist +IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@" +IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@" +IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@" +GOTO :INSTALLED + +:NOTINSTALLED +ECHO WARNING: This is a raw, uninstalled pear.bat + +REM Check to see if we can grab the directory of this file (Windows NT+) +IF %~n0 == pear ( +FOR %%x IN (cli\php.exe php.exe) DO (if "%%~$PATH:x" NEQ "" ( +SET "PHP_PEAR_PHP_BIN=%%~$PATH:x" +echo Using PHP Executable "%PHP_PEAR_PHP_BIN%" +"%PHP_PEAR_PHP_BIN%" -v +GOTO :NEXTTEST +)) +GOTO :FAILAUTODETECT +:NEXTTEST +IF "%PHP_PEAR_PHP_BIN%" NEQ "" ( + +REM We can use this PHP to run a temporary php file to get the dirname of pear + +echo ^ > ~~getloc.php +"%PHP_PEAR_PHP_BIN%" ~~getloc.php +set /p PHP_PEAR_BIN_DIR=fakeprompt < ~a.a +DEL ~a.a +DEL ~~getloc.php +set "PHP_PEAR_INSTALL_DIR=%PHP_PEAR_BIN_DIR%pear" + +REM Make sure there is a pearcmd.php at our disposal + +IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php ( +IF EXIST %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php COPY %PHP_PEAR_INSTALL_DIR%\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST pearcmd.php COPY pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +IF EXIST %~dp0\scripts\pearcmd.php COPY %~dp0\scripts\pearcmd.php %PHP_PEAR_INSTALL_DIR%\pearcmd.php +) +) +GOTO :INSTALLED +) ELSE ( +REM Windows Me/98 cannot succeed, so allow the batch to fail +) +:FAILAUTODETECT +echo WARNING: failed to auto-detect pear information +:INSTALLED + +REM Check Folders and files +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR +IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2 +IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR +IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR +REM launch pearcmd +GOTO RUN +:PEAR_INSTALL_ERROR +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_INSTALL_ERROR2 +ECHO PHP_PEAR_INSTALL_DIR is not set correctly. +ECHO pearcmd.php could not be found there. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_INSTALL_DIR% +GOTO END +:PEAR_BIN_ERROR +ECHO PHP_PEAR_BIN_DIR is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_BIN_DIR% +GOTO END +:PEAR_PHPBIN_ERROR +ECHO PHP_PEAR_PHP_BIN is not set correctly. +ECHO Please fix it using your environment variable or modify +ECHO the default value in pear.bat +ECHO The current value is: +ECHO %PHP_PEAR_PHP_BIN% +GOTO END +:RUN +"%PHP_PEAR_PHP_BIN%" -C -n -d date.timezone=UTC -d output_buffering=1 -d safe_mode=0 -d "include_path='%PHP_PEAR_INSTALL_DIR%'" -d register_argc_argv="On" -d variables_order=EGPCS -f "%PHP_PEAR_INSTALL_DIR%\peclcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9 +:END @ECHO ON \ No newline at end of file diff --git a/WEB-INF/lib/pear/scripts/peclcmd.php b/WEB-INF/lib/pear/scripts/peclcmd.php index 498caafd1..1c8bcb6bb 100644 --- a/WEB-INF/lib/pear/scripts/peclcmd.php +++ b/WEB-INF/lib/pear/scripts/peclcmd.php @@ -12,15 +12,16 @@ * @author Tomas V.V.Cox * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: peclcmd.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR */ /** * @nodep Gtk */ -if ('@include_path@' != '@'.'include_path'.'@') { - ini_set('include_path', '@include_path@'); +//the space is needed for windows include paths with trailing backslash +// http://pear.php.net/bugs/bug.php?id=19482 +if ('@include_path@ ' != '@'.'include_path'.'@ ') { + ini_set('include_path', trim('@include_path@ '). PATH_SEPARATOR . get_include_path()); $raw = false; } else { // this is a raw, uninstalled pear, either a cvs checkout, or php distro 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->mOnBlur!="") - $html .= " onblur=\"$this->mOnBlur\""; - - 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; } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/DefaultCellRenderer.class.php b/WEB-INF/lib/form/DefaultCellRenderer.class.php index adbd141e8..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 { @@ -32,8 +32,7 @@ class DefaultCellRenderer { var $mWidth = null; var $mOnChangeAdd = null; - function DefaultCellRenderer() { - + function __construct() { } function getValue() { return $this->mCellValue; } @@ -71,4 +70,3 @@ function render(&$table, $value, $row, $column, $selected = false) { return $this->toString(); } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/FloatField.class.php b/WEB-INF/lib/form/FloatField.class.php index 370b28b46..e6cbf1068 100644 --- a/WEB-INF/lib/form/FloatField.class.php +++ b/WEB-INF/lib/form/FloatField.class.php @@ -23,52 +23,51 @@ // | // +----------------------------------------------------------------------+ // | 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"; + var $mDelimiter = '.'; + var $mFFormat; + + function __construct($name) { + global $user; + + $this->class = 'FloatField'; + $this->name = $name; + $this->mDelimiter = $user->getDecimalMark(); + } + + function localize() { + global $user; + $this->mDelimiter = $user->getDecimalMark(); + } + + function setFormat($format) { + $this->mFFormat = $format; + } + + function setValue($value) { + if (isset($this->mFFormat) && isset($value) && strlen($value)) { + $value = str_replace($this->mDelimiter, '.', $value); + $value = sprintf('%'.$this->mFFormat.'f', $value); + $value = str_replace('.', $this->mDelimiter, $value); + } + $this->value = $value; + } + + function setValueSafe($value) { + // '.' to ',' , apply delimiter + if (strlen($value) > 0) + $this->value = str_replace('.', $this->mDelimiter, $value); + } - function FloatField($name) { - $this->mName = $name; - } - - function setLocalization($i18n) { - FormElement::setLocalization($i18n); - global $user; - $this->mDelimiter = $user->decimal_mark; - } - - function setFormat($format) { - $this->mFFormat = $format; - } - - function setValue($value) { - if (isset($this->mFFormat) && isset($value) && strlen($value)) { - $value = str_replace($this->mDelimiter,".",$value); - $value = sprintf("%".$this->mFFormat."f",$value); - $value = str_replace(".",$this->mDelimiter,$value); - } - $this->mValue = $value; - } - - function setValueSafe($value) { - // '.' to ',' , apply localisation - if (strlen($value)>0) - $this->mValue = str_replace(".",$this->mDelimiter,$value); - } - - function getValueSafe() { - // ',' to '.' - if (strlen($this->mValue)>0) { - return str_replace($this->mDelimiter,".",$this->mValue); - } else { - return null; - } - } + function getValueSafe() { + // ',' to '.' + if (strlen($this->value) > 0) + return str_replace($this->mDelimiter, '.', $this->value); + return null; + } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/Form.class.php b/WEB-INF/lib/form/Form.class.php index 340637d0e..cee608fe5 100644 --- a/WEB-INF/lib/form/Form.class.php +++ b/WEB-INF/lib/form/Form.class.php @@ -1,293 +1,182 @@ formName = $formid; - } - - function setRequest(&$request) { - $this->mRequest = &$request; - } - -/* function setFormBean(&$bean) { - $this->mFormBean = &$bean; - } -*/ - 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 setName($value) { $this->formName = $value; } - function getName() { return $this->formName; } - - function setId($value) { $this->mId = $value; } - function getId() { return $this->mId; } - - function setAction($value) { $this->mAction = $value; } - function getAction() { return $this->mAction; } - - function setMethod($value) { $this->mMethod = $value; } - function getMethod() { return $this->mMethod; } - - function setEnctype($value) { $this->mEnctype = $value; } - function getEnctype() { return $this->mEnctype; } - - 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; - + var $name = ''; // Form name. + var $elements = array(); // An array of input controls in form. + + function __construct($name) { + $this->name = $name; + } + + function getElement($name) { + return $this->elements[$name]; + } + + 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"]); - break; - - case "hidden": - import('form.Hidden'); - $el = new Hidden($arguments["name"]); + $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 "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->formName); - 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"]); + $el->setFormName($this->name); + 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->formName); - $this->mElements[$el->getName()] = &$el; + $el->setFormName($this->name); + $this->elements[$el->name] = &$el; } } function toStringOpenTag() { - $html = "
formName\""; + $html = "name\""; - if ($this->mId!="") - $html .= " id=\"$this->mId\""; - - if ($this->mAction!="") - $html .= " action=\"$this->mAction\""; + $html .= ' method="post"'; - if ($this->mMethod!="") - $html .= " method=\"$this->mMethod\""; - - // for upload forms - foreach ($this->mElements as $elname=>$el) { - if (strtolower(get_class($this->mElements[$elname]))=="uploadfile") { - $this->mEnctype = "multipart/form-data"; + // Add enctype for file upload forms. + foreach ($this->elements as $elname=>$el) { + if (strtolower(get_class($this->elements[$elname])) == 'uploadfile') { + $html .= ' enctype="multipart/form-data"'; + break; } } - - if ($this->mEnctype!="") - $html .= " enctype=\"$this->mEnctype\""; - + $html .= ">"; return $html; } 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 .= "
"; @@ -299,23 +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); } } } - -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/FormElement.class.php b/WEB-INF/lib/form/FormElement.class.php index 9e2739241..16b8d76d1 100644 --- a/WEB-INF/lib/form/FormElement.class.php +++ b/WEB-INF/lib/form/FormElement.class.php @@ -1,121 +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 setOnBlue($str) { $this->mOnBlur = $str; } - function setOnKeyPress($str){ $this->mOnKeyPress = $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()); + } } -?> \ No newline at end of file diff --git a/WEB-INF/lib/form/Hidden.class.php b/WEB-INF/lib/form/Hidden.class.php index 660870b47..89e90c7eb 100644 --- a/WEB-INF/lib/form/Hidden.class.php +++ b/WEB-INF/lib/form/Hidden.class.php @@ -23,32 +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 Hidden($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; + } } -?> \ No newline at end of file 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 .= '
-

 

- - - - - -
- {$i18n.footer.mobile_phones}   -
-
- - - - -
 Anuko Time Tracker 1.9.13.3389 | Copyright © Anuko | - {$i18n.footer.credits} | - {$i18n.footer.license} -
-
- -
+ {* page-content *} + + + + - \ No newline at end of file + 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 2d3bb2031..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 isset($tax)} + + + + {if $show_paid_column} + + {/if} + + + + + {if $show_paid_column} + + {/if} + + {/if} + + + + {if $show_paid_column} + + {/if} -
- - - - -{if $user->custom_logo} - - -
-{else} - -{/if} - - - - -
- - - + + Time Tracker {else} - + Anuko Time Tracker {/if} - -
Time TrackerAnuko Time Tracker
-
-
- - -{if $authenticated} - {if $user->isAdmin()} - - - - - -
  - {$i18n.menu.logout} · - {$i18n.menu.forum} · - {$i18n.menu.help} -
- + + +{* top menu for small screens *} + +{* end of top menu for small screens *} + +{* 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 in_array('ex', explode(',', $user->plugins)) && !$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()) && in_array('iv', explode(',', $user->plugins))} - · {$i18n.title.invoices} + {if $user->exists() && $user->isPluginEnabled('ts') && ($user->can('track_own_time') || $user->can('track_time'))} + {/if} - {if (in_array('ch', explode(',', $user->plugins)) && !$user->isClient()) && ($smarty.const.MODE_PROJECTS == $user->tracking_mode - || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode - || in_array('cl', explode(',', $user->plugins)))} - · {$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() && in_array('cl', explode(',', $user->plugins))} - · {$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:'html'}{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:'html'}{/if}{if $user->team}, {$user->team|escape:'html'}{/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 *} - -{if !$errors->isEmpty()} - - - - -
- {foreach $errors->getErrors() as $error} - {$error.message}
{* No need to escape as they are not coming from user and may contain a link. *} +{* 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. *} {/foreach} -
+ {/if} - +{* end of output errors *} - -{if !$messages->isEmpty()} - - - - -
- {foreach $messages->getErrors() as $message} - {$message.message}
{* No need to escape. *} +{* output messages *} +{if $msg->yes()} +
+ {foreach $msg->getErrors() as $message} +{$message.message}
{* No need to escape. *} {/foreach} -
+ {/if} - \ No newline at end of file +{* end of output messages *} + +
diff --git a/WEB-INF/templates/import.tpl b/WEB-INF/templates/import.tpl index ae7a002d3..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} \ No newline at end of file +
{$forms.importForm.btn_submit.control}
+{$forms.importForm.open} diff --git a/WEB-INF/templates/index.tpl b/WEB-INF/templates/index.tpl index fd7c5d96d..75a751e08 100644 --- a/WEB-INF/templates/index.tpl +++ b/WEB-INF/templates/index.tpl @@ -2,4 +2,4 @@ {if $content_page_name}{include file="$content_page_name"}{/if} -{include file="footer.tpl"} \ No newline at end of file +{include file="footer.tpl"} diff --git a/WEB-INF/templates/invoice_add.tpl b/WEB-INF/templates/invoice_add.tpl index 092e3ff85..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} - +
+ + + + + + + + + + + + + + + + + + +{if isset($show_project) && $show_project} + + + + + + +{/if} + + + + + + + - + + + + +
{$forms.invoiceForm.number.control}
{$forms.invoiceForm.date.control}
{$forms.invoiceForm.client.control}
{$forms.invoiceForm.project.control}
{$forms.invoiceForm.start.control}
- - - - - - - - - - - - - -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - -{/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}
{$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.finish.control}
{$i18n.label.required_fields}
+
{$forms.invoiceForm.btn_submit.control}
{$forms.invoiceForm.close} \ No newline at end of file + diff --git a/WEB-INF/templates/invoice_delete.tpl b/WEB-INF/templates/invoice_delete.tpl index 57823f3ec..951e5e816 100644 --- a/WEB-INF/templates/invoice_delete.tpl +++ b/WEB-INF/templates/invoice_delete.tpl @@ -1,23 +1,34 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + + + {$forms.invoiceDeleteForm.open} - +
+ - + + + + + + + +
{$i18n.form.invoice.invoice_to_delete}:
- - - - - - - - - - - - - - -
{$i18n.form.invoice.invoice_to_delete}:{$invoice_to_delete|escape:'html'}
{$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.close} \ No newline at end of file +
{$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 ceeeffc9b..801245559 100644 --- a/WEB-INF/templates/invoice_view.tpl +++ b/WEB-INF/templates/invoice_view.tpl @@ -1,74 +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:'html'}
{$i18n.label.date}: {$invoice_date}
{$i18n.label.client}: {$client_name|escape:'html'}
{$i18n.label.client_address}: {$client_address|escape:'html'}
-
{$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 $show_task} + {/if} - {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + + + + {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 ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - + {if $show_task} + {/if} - - - - + + + + {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.paid}{$i18n.label.note}{$i18n.label.duration}{$i18n.label.cost}
{$invoice_item.date}{$invoice_item.user_name|escape:'html'}{$invoice_item.project_name|escape:'html'}
{$invoice_item.date}{$invoice_item.user_name|escape}{$invoice_item.project_name|escape}{$invoice_item.task_name|escape:'html'}{$invoice_item.task_name|escape}{$invoice_item.note|escape:'html'}{$invoice_item.duration}{$invoice_item.cost}
{$invoice_item.note|escape}{$invoice_item.duration}{$invoice_item.cost}{if $invoice_item.paid}{$i18n.label.yes}{else}{$i18n.label.no}{/if}
 
{$i18n.label.subtotal}:{$subtotal|escape:'html'}
{$i18n.label.tax}:{$tax|escape:'html'}
{$i18n.label.total}:{$total|escape:'html'}
-{/if} -
{$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 ad8f53731..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} -{/if} - +{/foreach}
-{if $user->canManageTeam() || $user->isClient()} - - - - - - - {if !$user->isClient()} - - {/if} - - {foreach $invoices as $invoice} - - - - - - {if !$user->isClient()} - + + + + + + + + + + +
{$i18n.label.invoice}{$i18n.label.client}{$i18n.label.date}{$i18n.label.view}{$i18n.label.delete}
{$invoice.name|escape:'html'}{$invoice.client_name|escape:'html'}{$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} - - {/foreach} -
{$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}
- {if !$user->isClient()} - - -

+
{$i18n.label.delete}
+ +{if !$user->isClient()} +
+{/if} diff --git a/WEB-INF/templates/locking.tpl b/WEB-INF/templates/locking.tpl new file mode 100644 index 000000000..8ecf48cc9 --- /dev/null +++ b/WEB-INF/templates/locking.tpl @@ -0,0 +1,17 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.lockingForm.open} + + + + + + + +
{$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 ed00513d4..3d5aa9667 100644 --- a/WEB-INF/templates/login.db.tpl +++ b/WEB-INF/templates/login.db.tpl @@ -1,20 +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 833b0a125..e60d954ee 100644 --- a/WEB-INF/templates/login.ldap.tpl +++ b/WEB-INF/templates/login.ldap.tpl @@ -1,27 +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 85050cdea..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}
-{/if} \ No newline at end of file +
{$about_text}
+{/if} diff --git a/WEB-INF/templates/mail.tpl b/WEB-INF/templates/mail.tpl index 5d748afe3..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} (*):{$smarty.const.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.close} \ No newline at end of file +
{$forms.mailForm.btn_send.control}
+{$forms.mailForm.close} diff --git a/WEB-INF/templates/mobile/access_denied.tpl b/WEB-INF/templates/mobile/access_denied.tpl deleted file mode 100644 index fa0c45f25..000000000 --- a/WEB-INF/templates/mobile/access_denied.tpl +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/WEB-INF/templates/mobile/header.tpl b/WEB-INF/templates/mobile/header.tpl deleted file mode 100644 index 2a663c822..000000000 --- a/WEB-INF/templates/mobile/header.tpl +++ /dev/null @@ -1,83 +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 !$errors->isEmpty()} - - - - -
- {foreach $errors->getErrors() as $error} - {$error.message}
{* No need to escape as they are not coming from user and may contain a link. *} - {/foreach} -
-{/if} - - - -{if !$messages->isEmpty()} - - - - -
- {foreach $messages->getErrors() as $message} - {$message.message}
{* No need to escape. *} - {/foreach} -
-{/if} - \ No newline at end of file diff --git a/WEB-INF/templates/mobile/index.tpl b/WEB-INF/templates/mobile/index.tpl deleted file mode 100644 index 8d8993ca3..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} \ No newline at end of file diff --git a/WEB-INF/templates/mobile/login.db.tpl b/WEB-INF/templates/mobile/login.db.tpl deleted file mode 100644 index 11a7e7139..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}
\ No newline at end of file diff --git a/WEB-INF/templates/mobile/login.ldap.tpl b/WEB-INF/templates/mobile/login.ldap.tpl deleted file mode 100644 index 11a7e7139..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}
\ No newline at end of file diff --git a/WEB-INF/templates/mobile/login.tpl b/WEB-INF/templates/mobile/login.tpl deleted file mode 100644 index f70c37596..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} -
\ No newline at end of file diff --git a/WEB-INF/templates/mobile/time.tpl b/WEB-INF/templates/mobile/time.tpl deleted file mode 100644 index 8ae65b0d2..000000000 --- a/WEB-INF/templates/mobile/time.tpl +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - - - - - -
<<{$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:'html'}{if $record.duration == '0:00'}{/if}{$record.duration}{if $record.duration == '0:00'}{/if} - {if $record.invoice_id} {else}{$i18n.label.edit}{/if}
- - - - - -
{$i18n.label.day_total}:{$day_total}
- {/if} -
- -{$forms.timeRecordForm.open} - - - - -
- - - - - - - -
- -{if in_array('cl', explode(',', $user->plugins))} - - -{/if} -{if in_array('iv', explode(',', $user->plugins))} - -{/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:'html'}:
{$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} \ No newline at end of file diff --git a/WEB-INF/templates/mobile/time_delete.tpl b/WEB-INF/templates/mobile/time_delete.tpl deleted file mode 100644 index 31c6daa06..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:'html'}{if $time_rec.duration<>'0:00'}{$time_rec.duration}{else}{$i18n.form.time.uncompleted}{/if}{if $time_rec.comment}{$time_rec.comment|escape:'html'}{else} {/if}
- - - - - - - -
 
{$forms.timeRecordForm.delete_button.control}  {$forms.timeRecordForm.cancel_button.control}
-
-{$forms.timeRecordForm.close} \ No newline at end of file diff --git a/WEB-INF/templates/mobile/time_edit.tpl b/WEB-INF/templates/mobile/time_edit.tpl deleted file mode 100644 index 977f0a22e..000000000 --- a/WEB-INF/templates/mobile/time_edit.tpl +++ /dev/null @@ -1,254 +0,0 @@ - - -{$forms.timeRecordForm.open} - - - - -
- - - - -
- -{if in_array('cl', explode(',', $user->plugins))} - - -{/if} -{if in_array('iv', explode(',', $user->plugins))} - -{/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:'html'}:
{$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} \ No newline at end of file diff --git a/WEB-INF/templates/notification_add.tpl b/WEB-INF/templates/notification_add.tpl index fe209acfc..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.close} \ No newline at end of file +
{$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 4b8c617a0..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:'html'}
 
{$forms.notificationDeleteForm.btn_delete.control}  {$forms.notificationDeleteForm.btn_cancel.control}
-
-{$forms.notificationDeleteForm.close} \ No newline at end of file +
{$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 9234d2a4d..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.close} \ No newline at end of file +
{$forms.notificationForm.btn_submit.control}
+{$forms.notificationForm.close} diff --git a/WEB-INF/templates/notifications.tpl b/WEB-INF/templates/notifications.tpl index 78bd8b198..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:'html'}{$notification['cron_spec']|escape:'html'}{$notification['email']|escape:'html'}{$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.close} \ No newline at end of file +
{$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 c3cadbdc3..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} \ No newline at end of file +{$forms.newPasswordForm.close} diff --git a/WEB-INF/templates/password_reset.tpl b/WEB-INF/templates/password_reset.tpl index 17fe05a99..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} \ No newline at end of file +{$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 new file mode 100644 index 000000000..5e7473848 --- /dev/null +++ b/WEB-INF/templates/predefined_expense_add.tpl @@ -0,0 +1,21 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.predefinedExpenseForm.open} + + + + + + + + + + + + + + +
{$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 new file mode 100644 index 000000000..23a7193c8 --- /dev/null +++ b/WEB-INF/templates/predefined_expense_delete.tpl @@ -0,0 +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}
+{$forms.predefinedExpenseDeleteForm.close} diff --git a/WEB-INF/templates/predefined_expense_edit.tpl b/WEB-INF/templates/predefined_expense_edit.tpl new file mode 100644 index 000000000..06af4a315 --- /dev/null +++ b/WEB-INF/templates/predefined_expense_edit.tpl @@ -0,0 +1,21 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.predefinedExpenseForm.open} + + + + + + + + + + + + + + +
{$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 new file mode 100644 index 000000000..c0d45af9e --- /dev/null +++ b/WEB-INF/templates/predefined_expenses.tpl @@ -0,0 +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} +
{$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 032cc5f43..772172485 100644 --- a/WEB-INF/templates/profile_edit.tpl +++ b/WEB-INF/templates/profile_edit.tpl @@ -1,196 +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.lock_interval}:{$forms.profileForm.lock_interval.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.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}
{$forms.profileForm.notifications.control} {$i18n.label.configure}
 
{$forms.profileForm.btn_save.control}
-
{$forms.profileForm.email.control}
{$i18n.label.required_fields}
-{$forms.profileForm.close} \ No newline at end of file +
{$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 2bd7f6fc1..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} \ No newline at end of file +{$forms.projectForm.close} diff --git a/WEB-INF/templates/project_delete.tpl b/WEB-INF/templates/project_delete.tpl index bf1fc95f5..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:'html'}
 
{$forms.projectDeleteForm.btn_delete.control}  {$forms.projectDeleteForm.btn_cancel.control}
-
-{$forms.projectDeleteForm.close} \ No newline at end of file +
{$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 c94e47540..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.close} \ No newline at end of file +
{$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 8862d6a12..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:'html'}{$project.description|escape:'html'}{$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:'html'}{$project.description|escape:'html'}{$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:'html'}{$project.description|escape:'html'}
{$project.name|escape}{$project.description|escape}{$i18n.label.files} 
+ {/if} - - - \ No newline at end of file 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 new file mode 100644 index 000000000..1c38bb322 --- /dev/null +++ b/WEB-INF/templates/quotas.tpl @@ -0,0 +1,49 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +
{$i18n.form.quota.hint}
+{$forms.monthlyQuotasForm.open} + + + + + + + + + + + + + +
{$forms.monthlyQuotasForm.workdayHours.control}
{$forms.monthlyQuotasForm.year.control}
+
+ + + + + +{foreach $months as $month} + + + + +{/foreach} +
{$i18n.form.quota.month}{$i18n.label.quota}
{$month}:{$forms.monthlyQuotasForm.$month.control}
+
+{$forms.monthlyQuotasForm.close} + + diff --git a/WEB-INF/templates/register.tpl b/WEB-INF/templates/register.tpl index 135230b74..b8a92e2a9 100644 --- a/WEB-INF/templates/register.tpl +++ b/WEB-INF/templates/register.tpl @@ -1,47 +1,64 @@ -{$forms.profileForm.open} - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{$i18n.label.team_name}:{$forms.profileForm.team_name.control}
{$i18n.label.currency}:{$forms.profileForm.currency.control}
 
{$i18n.label.manager_name} (*):{$forms.profileForm.manager_name.control}
{$i18n.label.manager_login} (*):{$forms.profileForm.manager_login.control}
{$i18n.label.password} (*):{$forms.profileForm.password1.control}
{$i18n.label.confirm_password} (*):{$forms.profileForm.password2.control}
{$i18n.label.email}:{$forms.profileForm.manager_email.control}
{$i18n.label.required_fields}
 
{$forms.profileForm.btn_submit.control}
+{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + +{$forms.groupForm.open} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{$forms.groupForm.group_name.control} + {$i18n.label.what_is_it} + {$i18n.label.what_is_it}
{$forms.groupForm.currency.control}
{$forms.groupForm.lang.control}
{$forms.groupForm.manager_name.control}
{$forms.groupForm.manager_login.control}
{$forms.groupForm.password1.control}
{$forms.groupForm.password2.control}
{$forms.groupForm.manager_email.control}
{$i18n.label.required_fields}
{$forms.groupForm.btn_submit.control}
-{$forms.profileForm.close} \ No newline at end of file +{$forms.groupForm.close} \ No newline at end of file diff --git a/WEB-INF/templates/report.tpl b/WEB-INF/templates/report.tpl index 6192b1465..b63e505f8 100644 --- a/WEB-INF/templates/report.tpl +++ b/WEB-INF/templates/report.tpl @@ -1,163 +1,344 @@ +{* Copyright (c) Anuko International Ltd. https://www.anuko.com +License: See license.txt *} + -{$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:'html'}{$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:'html'}{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:'html'} {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:'html'}{$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} - - {$cur_date = $item.date} + {* 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} + {$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} - - {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} + {* print a terminating subtotal *} + {if $print_subtotals} + + + {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:'html'}{if $group_by == 'client'}{$subtotals[$prev_grouped_by]['name']|escape:'html'}{if $group_by == 'project'}{$subtotals[$prev_grouped_by]['name']|escape:'html'}{if $group_by == 'task'}{$subtotals[$prev_grouped_by]['name']|escape:'html'}{if $group_by == 'cf_1'}{$subtotals[$prev_grouped_by]['name']|escape:'html'}
{$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:'html'}{$item.client|escape:'html'}{$item.project|escape:'html'}{$item.task|escape:'html'}{$item.cf_1|escape:'html'}{$item.start}{$item.finish}{$item.duration}{$item.note|escape:'html'}{if $user->canManageTeam() || $user->isClient()}{$item.cost}{else}{$item.expense}{/if}{$item.invoice|escape:'html'}
{$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:'html'}{if $group_by == 'client'}{$subtotals[$cur_grouped_by]['name']|escape:'html'}{if $group_by == 'project'}{$subtotals[$cur_grouped_by]['name']|escape:'html'}{if $group_by == 'task'}{$subtotals[$cur_grouped_by]['name']|escape:'html'}{if $group_by == 'cf_1'}{$subtotals[$cur_grouped_by]['name']|escape:'html'}
{$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:'html'} {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 $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} + + + + + + + {/if} + {if $use_assign_to_timesheet} + - + + -
{$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}
-
\ No newline at end of file +
+ {/if} + +{/if} +{$forms.reportViewForm.close} +
diff --git a/WEB-INF/templates/reports.tpl b/WEB-INF/templates/reports.tpl index 7e4c90bdc..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}
+
+ + + + + + + + + + - - - - {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - {/if} - +{if $show_inactive_users} +
{$i18n.label.inactive_users}
+
{$i18n.form.reports.select_period}
{$forms.reportForm.period.control}
{$i18n.form.reports.set_period}
- -{if ((in_array('cl', explode(',', $user->plugins)) && !($user->isClient() && $user->client_id)) || ($custom_fields && $custom_fields->fields[0] && $custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN))} - - {if in_array('cl', explode(',', $user->plugins)) && !($user->isClient() && $user->client_id)}{else}{/if} - - {if ($custom_fields && $custom_fields->fields[0] && $custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)}{else}{/if} - - - - - - + + + + + + + + + + +
{$i18n.label.client}  {$i18n.label.option} 
{$forms.reportForm.client.control} {$forms.reportForm.option.control}
{$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 ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} -
{$i18n.label.project} {$i18n.label.task}
+ +
{$forms.reportForm.users_inactive.control}
+
{/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - {$forms.reportForm.project.control} -   - {if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - {$forms.reportForm.task.control} - {/if} - + +{if $show_client} + + + + + + {/if} -{if in_array('iv', explode(',', $user->plugins))} - - - - - - - - - - +{if $show_billable} + + + + + + {/if} -{if $user->canManageTeam() || $user->isClient()} - - - - - - +{if $show_invoice_dropdown} + + + + + + +{/if} +{if $show_paid_status} + + + + + + {/if} - - - - - - - - - - - - - - - - - - - - - - - - - -
{$forms.reportForm.client.control}
{$i18n.form.time.billable} {$i18n.label.invoice}
{$forms.reportForm.include_records.control} {$forms.reportForm.invoice.control}
{$forms.reportForm.include_records.control}
{$i18n.label.users}
{$forms.reportForm.users.control}
{$forms.reportForm.invoice.control}
{$forms.reportForm.paid_status.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 in_array('cl', explode(',', $user->plugins)) || in_array('iv', explode(',', $user->plugins))} - - {if in_array('cl', explode(',', $user->plugins))} - - {/if} - {if ($user->canManageTeam() || $user->isClient()) && in_array('iv', explode(',', $user->plugins))} - - {/if} - -{/if} - - - - -{if ((($user->canManageTeam() || $user->isClient()) || in_array('ex', explode(',', $user->plugins))) && defined('COST_ON_REPORTS') && isTrue($smarty.const.COST_ON_REPORTS))} - -{else} - +{if $show_project} + + + + + + {/if} - - - - - -{if ($custom_fields && $custom_fields->fields[0])} - -{else} - +{if $show_task} + + + + + + {/if} - -
{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.project.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.task.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}
+{if $show_approved} + + + + {$forms.reportForm.approved.control} + +
+{/if} +{if $show_timesheet_dropdown} + + + + {$forms.reportForm.timesheet.control} + +
+{/if} + + + + {$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.close} \ No newline at end of file +
{$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 9b568c8a9..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} \ No newline at end of file +{$forms.taskForm.close} diff --git a/WEB-INF/templates/task_delete.tpl b/WEB-INF/templates/task_delete.tpl index 1b2d52729..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:'html'}
 
{$forms.taskDeleteForm.btn_delete.control}  {$forms.taskDeleteForm.btn_cancel.control}
-
-{$forms.taskDeleteForm.close} \ No newline at end of file +
{$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 b82a093d3..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.close} \ No newline at end of file +
{$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 54d5be56b..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:'html'}{$task.description|escape:'html'}{$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:'html'}{$task.description|escape:'html'}{$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:'html'}{$task.description|escape:'html'}
{$task.name|escape}{$task.description|escape}
- {/if} - - - \ No newline at end of file + +{/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 936706567..fa24e8062 100644 --- a/WEB-INF/templates/time.tpl +++ b/WEB-INF/templates/time.tpl @@ -1,361 +1,246 @@ - - +{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} - +
{$forms.timeRecordForm.date.control}
+
+ +{if isset($user_dropdown)} + - - +{if $show_files} + + + + -
{$forms.timeRecordForm.date.control}
- -{if $on_behalf_control} - - - - + + + + {/if} -{if in_array('cl', explode(',', $user->plugins))} - - - - +{if $show_client} + + + + + + {/if} -{if in_array('iv', explode(',', $user->plugins))} - - - - +{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} -
{$i18n.label.user}:{$forms.timeRecordForm.onBehalfUser.control}
{$forms.timeRecordForm.user.control}
{$i18n.label.client}{if in_array('cm', explode(',', $user->plugins))} (*){/if}:{$forms.timeRecordForm.client.control}
{$forms.timeRecordForm.client.control}
 
 
{$custom_fields->fields[0]['label']|escape:'html'}{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 in_array('cl', explode(',', $user->plugins))} - -{/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} - - - - - {foreach $time_records as $record} - -{if in_array('cl', explode(',', $user->plugins))} - -{/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 $time_records} + + + +
+
{$i18n.label.client}{$i18n.label.project}{$i18n.label.task}{$i18n.label.start}{$i18n.label.finish}{$i18n.label.duration}{$i18n.label.note}{$i18n.label.edit}
{$record.client|escape:'html'}{$record.project|escape:'html'}{$record.task|escape:'html'}{if $record.start}{$record.start}{else} {/if}{if $record.finish}{$record.finish}{else} {/if}{if $record.duration <> '0:00'}{$record.duration}{else}{$i18n.form.time.uncompleted}{/if}{if $record.comment}{$record.comment|escape:'html'}{else} {/if} - {if $record.invoice_id} -   - {else} - {$i18n.label.edit} - {if $record.duration == '0:00'} - - - - - {/if} - {/if} -
+ + {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} -
{$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}
{/if} -
-{if $time_records} - + {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}
{$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} + {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.week_total}: {$week_total}{$i18n.label.day_total}: {$day_total}{$i18n.label.note}:{$record.comment|escape}
+
{/if} {$forms.timeRecordForm.close} - +
+ + + + + +{if $user->isPluginEnabled('mq')} + + + {if $over_balance} + + {else} + + {/if} + + + + {if $over_quota} + + {else} + + {/if} + +{/if} +
{$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.quota}: {$month_quota}{$i18n.form.time.over_quota}: {$quota_remaining}{$i18n.form.time.remaining_quota}: {$quota_remaining}
+
diff --git a/WEB-INF/templates/time_delete.tpl b/WEB-INF/templates/time_delete.tpl index 946a21e19..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:'html'}
{$time_rec.project_name|escape}{$time_rec.task_name|escape:'html'}{$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.duration}{else}{$i18n.form.time.uncompleted}{/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:'html'}{else} {/if}
- - - - - - + -
 
{$forms.timeRecordForm.delete_button.control}  {$forms.timeRecordForm.cancel_button.control}{if $time_rec.comment}{$time_rec.comment|escape}{else} {/if}
-
-{$forms.timeRecordForm.close} \ No newline at end of file +
{$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 ba8974fe1..8425965b9 100644 --- a/WEB-INF/templates/time_edit.tpl +++ b/WEB-INF/templates/time_edit.tpl @@ -1,275 +1,116 @@ - +{/if} {$forms.timeRecordForm.open} - - - + + + + +
- +
+ +{if $show_client} + + + + + + +{/if} +{if $show_billable} - - -
{$forms.timeRecordForm.client.control}
- -{if in_array('cl', explode(',', $user->plugins))} - - - - + + + + {/if} -{if in_array('iv', explode(',', $user->plugins))} - - - - +{if $show_paid_status} + + + + + {/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 $show_project} + + + + + + {/if} -{if ($smarty.const.MODE_PROJECTS == $user->tracking_mode || $smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - +{if $show_task} + + + + + + {/if} -{if ($smarty.const.MODE_PROJECTS_AND_TASKS == $user->tracking_mode)} - - - - +{if $show_start} + + + + + + + + + + + + {/if} -{if (($smarty.const.TYPE_START_FINISH == $user->record_type) || ($smarty.const.TYPE_ALL == $user->record_type))} - - - - - - - - +{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 in_array('cm', explode(',', $user->plugins))} (*){/if}:{$forms.timeRecordForm.client.control}
 
 
 
{$custom_fields->fields[0]['label']|escape:'html'}{if $custom_fields->fields[0]['required']} (*){/if}:{$forms.timeRecordForm.cf_1.control}
{$forms.timeRecordForm.$control_name.control}
{$forms.timeRecordForm.project.control}
{$i18n.label.project} (*):{$forms.timeRecordForm.project.control}
{$forms.timeRecordForm.task.control}
{$i18n.label.task} (*):{$forms.timeRecordForm.task.control}
{$forms.timeRecordForm.start.control} {$i18n.button.now}
{$forms.timeRecordForm.finish.control}{$i18n.button.now}
{$i18n.label.start}:{$forms.timeRecordForm.start.control} 
{$i18n.label.finish}:{$forms.timeRecordForm.finish.control} 
{$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.close} \ No newline at end of file +
{$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 new file mode 100644 index 000000000..0dafae8cd --- /dev/null +++ b/WEB-INF/templates/time_script.tpl @@ -0,0 +1,344 @@ + 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 144a8e9c2..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} \ No newline at end of file +{$forms.userForm.close} diff --git a/WEB-INF/templates/user_delete.tpl b/WEB-INF/templates/user_delete.tpl index 291dc4b5a..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:'html'}
 
{$forms.userDeleteForm.btn_delete.control}  {$forms.userDeleteForm.btn_cancel.control}
-
-{$forms.userDeleteForm.close} \ No newline at end of file +
{$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 5fa98694a..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} \ No newline at end of file +{$forms.userForm.close} diff --git a/WEB-INF/templates/users.tpl b/WEB-INF/templates/users.tpl index f343f9cf1..2a226238d 100644 --- a/WEB-INF/templates/users.tpl +++ b/WEB-INF/templates/users.tpl @@ -1,127 +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} - + + + + + {if $show_quota} + {/if} - {if $user->isManager()} - - - + {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}
{$u.name|escape:'html'}{$u.login|escape:'html'}{$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} + {$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 $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:'html'}{$u.login|escape:'html'}{$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:'html'}{$u.login|escape:'html'}{$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} - \ No newline at end of file + {/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 f046a5352..80900d570 100644 --- a/access_denied.php +++ b/access_denied.php @@ -1,37 +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'); -?> \ No newline at end of file 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 ac90921b8..aed852320 100644 --- a/admin_options.php +++ b/admin_options.php @@ -1,71 +1,76 @@ getMethod() == 'POST') { - $cl_password1 = $request->getParameter('password1'); - $cl_password2 = $request->getParameter('password2'); +$cl_name = $cl_login = $cl_password1 = $cl_password2 = $cl_email = ''; +if ($request->isPost()) { + $cl_name = trim($request->getParameter('name')); + $cl_login = trim($request->getParameter('login')); + if (!$auth->isPasswordExternal()) { + $cl_password1 = $request->getParameter('password1'); + $cl_password2 = $request->getParameter('password2'); + } + $cl_email = trim($request->getParameter('email')); +} else { + $cl_name = $user->name; + $cl_login = $user->login; + $cl_email = $user->email; } $form = new Form('optionsForm'); -$form->addInput(array('type'=>'text','aspassword'=>true,'maxlength'=>'30','name'=>'password1','style'=>'width: 150px;','value'=>$cl_password1)); -$form->addInput(array('type'=>'text','aspassword'=>true,'maxlength'=>'30','name'=>'password2','style'=>"width: 150px;",'value'=>$cl_password2)); -$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','maxlength'=>'100','name'=>'login','value'=>$cl_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'=>'email','value'=>$cl_email)); +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.submit'))); -if ($request->getMethod() == 'POST') { - if ($cl_password1 || $cl_password2) { - // Validate user input. - if (!ttValidString($cl_password1)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); - if ($cl_password1 !== $cl_password2) - $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } +if ($request->isPost()) { + // Validate user input. + 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->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 ($errors->isEmpty() && $cl_password1) { - if (ttUserHelper::setPassword($user->id, $cl_password1)) { - header('Location: admin_teams.php'); - exit(); - } else - $errors->add($i18n->getKey('error.db')); + 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(); } -} // post +} // 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'); -?> \ No newline at end of file diff --git a/admin_team_add.php b/admin_team_add.php deleted file mode 100644 index b41571c76..000000000 --- a/admin_team_add.php +++ /dev/null @@ -1,104 +0,0 @@ -getMethod() == 'POST') { - $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->getMethod() == 'POST') { - // Validate user input. - if (!ttValidString($cl_team_name, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.team_name')); - if (!ttValidString($cl_manager_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_name')); - if (!ttValidString($cl_manager_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_login')); - if (!$auth->isPasswordExternal()) { - if (!ttValidString($cl_password1)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); - if ($cl_password1 !== $cl_password2) - $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } - if (!ttValidEmail($cl_manager_email, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - - if ($errors->isEmpty()) { - 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 - $errors->add($i18n->getKey('error.db')); - } else - $errors->add($i18n->getKey('error.user_exists')); - } -} - -$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'); -?> \ No newline at end of file diff --git a/admin_team_delete.php b/admin_team_delete.php deleted file mode 100644 index 250031844..000000000 --- a/admin_team_delete.php +++ /dev/null @@ -1,68 +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->getMethod() == 'POST') { - if ($request->getParameter('btn_delete')) { - if (ttTeamHelper::markDeleted($team_id)) { - header('Location: admin_teams.php'); - exit(); - } else - $errors->add($i18n->getKey('error.db')); - } - - if ($request->getParameter('btn_cancel')) { - header('Location: admin_teams.php'); - exit(); - } -} - -$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'); -?> \ No newline at end of file diff --git a/admin_team_edit.php b/admin_team_edit.php deleted file mode 100644 index c6181a682..000000000 --- a/admin_team_edit.php +++ /dev/null @@ -1,123 +0,0 @@ -getParameter('id'); -$team_details = ttTeamHelper::getTeamDetails($team_id); - -if ($request->getMethod() == 'POST') { - $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->getMethod() == 'POST') { - if ($request->getParameter('btn_save')) { - // Validate user input. - if (!ttValidString($cl_team_name, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.team_name')); - if (!ttValidString($cl_manager_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_name')); - if (!ttValidString($cl_manager_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_login')); - if (!$auth->isPasswordExternal() && ($cl_password1 || $cl_password2)) { - if (!ttValidString($cl_password1)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); - if ($cl_password1 !== $cl_password2) - $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } - if (!ttValidEmail($cl_manager_email, true)) $errors->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)) $errors->add($i18n->getKey('error.user_exists')); - - if ($errors->isEmpty()) { - $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 - $errors->add($i18n->getKey('error.db')); - } - } - - if ($request->getParameter('btn_cancel')) { - header('Location: admin_teams.php'); - exit(); - } -} // POST - -$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'); -?> \ No newline at end of file diff --git a/cf_custom_field_add.php b/cf_custom_field_add.php index 1818b5722..87d46bcd5 100644 --- a/cf_custom_field_add.php +++ b/cf_custom_field_add.php @@ -1,75 +1,60 @@ isPluginEnabled('cf')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. -if ($request->getMethod() == 'POST') { +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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_field_name)) $errors->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 ($errors->isEmpty()) { - $res = CustomFields::insertField($cl_field_name, $cl_field_type, $cl_required); + if ($err->no()) { + $res = CustomFields::insertField($cl_field_name, $cl_entity_type, $cl_field_type, $cl_required); if ($res) { header('Location: cf_custom_fields.php'); exit(); } else - $errors->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'); -?> \ No newline at end of file diff --git a/cf_custom_field_delete.php b/cf_custom_field_delete.php index 6f42e31f9..ba222c657 100644 --- a/cf_custom_field_delete.php +++ b/cf_custom_field_delete.php @@ -1,77 +1,54 @@ 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'); -if ($request->getMethod() == 'POST') { - if ($request->getParameter('btn_delete')) { - // Delete button pressed. - $res = CustomFields::deleteField($id); +if ($request->isPost()) { + if ($request->getParameter('btn_delete')) { + // Delete button pressed. + $res = CustomFields::deleteField($id); if ($res) { header('Location: cf_custom_fields.php'); exit(); - } else { - $errors->add($i18n->getKey('error.db')); - } + } else + $err->add($i18n->get('error.db')); } if ($request->getParameter('btn_cancel')) { - // Cancel button pressed. - header('Location: cf_custom_fields.php'); - exit(); + // Cancel button pressed. + header('Location: cf_custom_fields.php'); + exit(); } } else { - $field = CustomFields::getField($id); - if (false === $field) - $errors->add($i18n->getKey('error.db')); - - if ($errors->isEmpty()) { - $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'); -?> \ No newline at end of file diff --git a/cf_custom_field_edit.php b/cf_custom_field_edit.php index 796ad1727..3f959ee22 100644 --- a/cf_custom_field_edit.php +++ b/cf_custom_field_edit.php @@ -1,82 +1,72 @@ 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) - $errors->add($i18n->getKey('error.db')); +if (!$field) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. $form = new Form('fieldForm'); -if ($errors->isEmpty()) { +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->getMethod() == 'POST') { +if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_type = $request->getParameter('type'); $cl_required = $request->getParameter('required'); if (!$cl_required) $cl_required = 0; - + // Validate user input. - if (!ttValidString($cl_name)) $errors->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 ($errors->isEmpty()) { + if ($err->no()) { $res = CustomFields::updateField($cl_id, $cl_name, $cl_type, $cl_required); if ($res) { header('Location: cf_custom_fields.php'); exit(); - } else { - $errors->add($i18n->getKey('error.db')); - } + } else + $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'); -?> \ No newline at end of file diff --git a/cf_custom_fields.php b/cf_custom_fields.php index 942817db6..6552bb7f6 100644 --- a/cf_custom_fields.php +++ b/cf_custom_fields.php @@ -1,61 +1,38 @@ isPluginEnabled('cf')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. $form = new Form('customFieldsForm'); -if ($request->getMethod() == 'POST') { +if ($request->isPost()) { if ($request->getParameter('btn_add')) { - // The Add button clicked. Redirect to cf_custom_field_add.php page. - header('Location: cf_custom_field_add.php'); - exit(); + // The Add button clicked. Redirect to cf_custom_field_add.php page. + header('Location: cf_custom_field_add.php'); + 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'); -?> \ No newline at end of file diff --git a/cf_dropdown_option_add.php b/cf_dropdown_option_add.php index d13c33bef..582794ae5 100644 --- a/cf_dropdown_option_add.php +++ b/cf_dropdown_option_add.php @@ -1,73 +1,53 @@ 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) - $errors->add($i18n->getKey('error.db')); - +if (!$field) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. + $form = new Form('optionAddForm'); -if ($errors->isEmpty()) { +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->getMethod() == 'POST') { + +if ($request->isPost()) { $cl_option_name = trim($request->getParameter('name')); - + // Validate user input. - if (!ttValidString($cl_option_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - - if ($errors->isEmpty()) { - $res = CustomFields::insertOption($cl_field_id, $cl_option_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); if ($res) { header("Location: cf_dropdown_options.php?field_id=$cl_field_id"); exit(); - } else { - $errors->add($i18n->getKey('error.db')); - } + } else + $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'); -?> \ No newline at end of file diff --git a/cf_dropdown_option_delete.php b/cf_dropdown_option_delete.php index 8ae945679..50c685014 100644 --- a/cf_dropdown_option_delete.php +++ b/cf_dropdown_option_delete.php @@ -1,79 +1,57 @@ 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->getMethod() == 'POST') { +if ($request->isPost()) { // Determine field id for redirect. $field_id = CustomFields::getFieldIdForOption($cl_id); if ($request->getParameter('btn_delete')) { - // Delete button pressed. - $res = CustomFields::deleteOption($cl_id); + // Delete button pressed. + $res = CustomFields::deleteOption($cl_id); if ($res) { header("Location: cf_dropdown_options.php?field_id=$field_id"); exit(); - } else { - $errors->add($i18n->getKey('error.db')); - } + } else + $err->add($i18n->get('error.db')); } if ($request->getParameter('btn_cancel')) { - // Cancel button pressed. - header("Location: cf_dropdown_options.php?field_id=$field_id"); - exit(); + // Cancel button pressed. + header("Location: cf_dropdown_options.php?field_id=$field_id"); + exit(); } } else { - $option = CustomFields::getOptionName($cl_id); - if (false === $option) - $errors->add($i18n->getKey('error.db')); - - if ($errors->isEmpty()) { - $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'); -?> \ No newline at end of file diff --git a/cf_dropdown_option_edit.php b/cf_dropdown_option_edit.php index bd190435d..2477b057c 100644 --- a/cf_dropdown_option_edit.php +++ b/cf_dropdown_option_edit.php @@ -1,75 +1,55 @@ 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) - $errors->add($i18n->getKey('error.db')); +if ($cl_name == null) { + header('Location: access_denied.php'); + exit(); +} +// End of access checks. $form = new Form('optionEditForm'); -if ($errors->isEmpty()) { - $form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'name','value'=>$cl_name)); +if ($err->no()) { + $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->getMethod() == 'POST') { +if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); - + // Validate user input. - if (!ttValidString($cl_name)) $errors->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 ($errors->isEmpty()) { + if ($err->no()) { $res = CustomFields::updateOption($cl_id, $cl_name); if ($res) { // Determine field id for redirect. $field_id = CustomFields::getFieldIdForOption($cl_id); header("Location: cf_dropdown_options.php?field_id=$field_id"); exit(); - } else { - $errors->add($i18n->getKey('error.db')); - } + } else + $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'); -?> \ No newline at end of file diff --git a/cf_dropdown_options.php b/cf_dropdown_options.php index c8713caff..06f53a7d8 100644 --- a/cf_dropdown_options.php +++ b/cf_dropdown_options.php @@ -1,52 +1,32 @@ 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) - $errors->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'); -?> \ No newline at end of file diff --git a/charts.php b/charts.php index 5e26978e0..9dbb95c70 100644 --- a/charts.php +++ b/charts.php @@ -1,185 +1,213 @@ 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 (in_array('cl', explode(',', $user->plugins))) - $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 (!in_array('cl', explode(',', $user->plugins))) - $cl_type = CHART_PROJECTS; - } else if ($cl_type == CHART_TASKS) { - if (MODE_PROJECTS_AND_TASKS != $user->tracking_mode) - $cl_type = CHART_PROJECTS; - } -} -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(); - } - } + // 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; } // 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 - || in_array('cl', explode(',', $user->plugins))); -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 (in_array('cl', explode(',', $user->plugins))) - $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); -$smarty->assign('totals', $totals); +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. /* @@ -188,7 +216,7 @@ * auto-calculated percentage markers around it. We print labels (to the side of the picture) ourselves, * using the same colors libchart is using. For labels printout, the $totals array (which is used for picture points) * is also passed to charts.tpl Smarty template. - * + * * To make all of the above possible with only one database call to obtain $totals we have to print the chart image * to a file here (see code below). Once the image is available as a .png file, the charts.tpl can render it. * @@ -200,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); + } } } @@ -221,10 +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'); -?> \ No newline at end of file diff --git a/client_add.php b/client_add.php index 503b94031..f1f45333e 100644 --- a/client_add.php +++ b/client_add.php @@ -1,45 +1,27 @@ isPluginEnabled('cl')) { + header('Location: feature_disabled.php'); + exit(); +} -$projects = ttTeamHelper::getActiveProjects($user->team_id); +$projects = ttGroupHelper::getActiveProjects(); -if ($request->getMethod() == 'POST') { +$cl_name = $cl_address = $cl_tax = ''; +$cl_projects = array(); +if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_address = trim($request->getParameter('address')); $cl_tax = $request->getParameter('tax'); @@ -47,27 +29,28 @@ } 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']; + // $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)); -$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->getMethod() == 'POST') { +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->get('button.add'))); + +if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.client_name')); - if (!ttValidString($cl_address, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.client_address')); - if (!ttValidFloat($cl_tax, true)) $errors->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 ($errors->isEmpty()) { + 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, @@ -75,15 +58,15 @@ header('Location: clients.php'); exit(); } else - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $errors->add($i18n->getKey('error.client_exists')); + $err->add($i18n->get('error.object_exists')); } -} // post - +} // 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'); -?> \ No newline at end of file diff --git a/client_delete.php b/client_delete.php index d03ef9c9f..e832c6bcb 100644 --- a/client_delete.php +++ b/client_delete.php @@ -1,76 +1,54 @@ 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'))); +$form->addInput(array('type'=>'combobox','name'=>'delete_client_entries', + '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->getMethod() == 'POST') { - if(ttClientHelper::getClient($id)) { - if ($request->getParameter('btn_delete')) { - if (ttClientHelper::delete($id, $request->getParameter('delete_client_entries'))) { - header('Location: clients.php'); - exit(); - } else - $errors->add($i18n->getKey('error.db')); - } - } else - $errors->add($i18n->getKey('error.db')); +if ($request->isPost()) { + 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'); - exit(); + header('Location: clients.php'); + exit(); } -} // post +} // 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('title', $i18n->get('title.delete_client')); $smarty->assign('content_page_name', 'client_delete.tpl'); $smarty->display('index.tpl'); -?> \ No newline at end of file diff --git a/client_edit.php b/client_edit.php index 400286d01..665fda15e 100644 --- a/client_edit.php +++ b/client_edit.php @@ -1,54 +1,40 @@ 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); - -if ($request->getMethod() == 'POST') { +$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')); $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']; @@ -59,25 +45,29 @@ } } +$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')))); -$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'))); - -if ($request->getMethod() == 'POST') { + '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->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)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.client_name')); - if (!ttValidString($cl_address, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.client_address')); - if (!ttValidFloat($cl_tax, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.tax')); - - if ($errors->isEmpty()) { - if ($request->getParameter('btn_save')) { + 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')) { $client = ttClientHelper::getClientByName($cl_name); if (($client && ($cl_id == $client['id'])) || !$client) { if (ttClientHelper::update(array( @@ -90,16 +80,14 @@ header('Location: clients.php'); exit(); } else - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $errors->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, @@ -107,15 +95,15 @@ header('Location: clients.php'); exit(); } else - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $errors->add($i18n->getKey('error.client_exists')); + $err->add($i18n->get('error.object_exists')); } } -} // post - +} // 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'); -?> \ No newline at end of file diff --git a/clients.php b/clients.php index 61c66583b..309eb155a 100644 --- a/clients.php +++ b/clients.php @@ -1,44 +1,31 @@ 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'); -?> \ No newline at end of file diff --git a/cron.php b/cron.php index 22b4991d3..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; + exit(); while ($val = $res->fetchRow()) { // We have jobs to execute in user language. - + // Get favorite report details. - $report = ttFavReportHelper::getReport($val['report_id']); - if (!$report) continue; - - // Recycle global $user and $i18n objects, as user settings and language are specific for each report. - $user = new ttUser(null, $report['user_id']); + $options = ttFavReportHelper::getReportOptions($val['report_id']); + if (!$options) continue; // Skip not found report. + + // 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...
"; - - // Calculate next execution time. - $next = tdCron::getNextOccurrence($val['cron_spec'], $now); + // 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); @@ -80,5 +86,3 @@ } echo "Done!"; - -?> \ No newline at end of file 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 index c65358a81..44fede0cd 100644 --- a/dbinstall.php +++ b/dbinstall.php @@ -1,99 +1,249 @@ \n"); -} require_once('initialize.php'); import('ttUserHelper'); import('ttTaskHelper'); +import('ttRoleHelper'); +import('ttOrgHelper'); + +// TODO: we need an installer. + +// ttExecute - executes an sql statement and prints either an error or a success message. +function ttExecute($sql) { + print "
".$sql."
"; + $mdb2 = getConnection(); + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) + print 'Error: '.$affected->getMessage().'.
'; + else + 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.
'); + } else { + echo('Error: WEB-INF/templates_c/ directory is not writable.
'); + } + + // Require the configuration file with application settings. + if (file_exists(APP_DIR."/WEB-INF/config.php")) { + 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 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 a PHP opening tag. See explanation.
'); + } + fclose($file); + } else { + echo('Error: WEB-INF/config.php file does not exist.
'); + } + + // Check whether DSN is defined. + if (defined('DSN')) { + // echo('DSN is defined as '.DSN.'
'); + echo('DSN is defined.
'); + } else { + echo('Error: DSN value is not defined. Check your config.php file.
'); + } + + // Check if PHP version is 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 { + 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. + if (strrpos(DSN, 'mysqli://', -strlen(DSN)) !== FALSE) { + if (extension_loaded('mysqli')) { + echo('mysqli PHP extension is loaded.
'); + } else { + echo('Error: mysqli PHP extension is required but is not loaded.
'); + } + } + if (strrpos(DSN, 'mysql://', -strlen(DSN)) !== FALSE) { + if (extension_loaded('mysql')) { + echo('mysql PHP extension is loaded.
'); + } else { + echo('Error: mysql PHP extension is required but is not loaded.
'); + } + } + + // Check mbstring extension. + if (extension_loaded('mbstring')) { + echo('mbstring PHP extension is loaded.
'); + } else { + echo('Error: mbstring PHP extension is not loaded.
'); + } + + // Check gd extension. + if (extension_loaded('gd')) { + echo('gd PHP extension is loaded.
'); + } else { + echo('Error: gd PHP extension is not loaded. It is required for charts plugin.
'); + } + + // Check ldap extension. + if (AUTH_MODULE == '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); + if (!is_a($conn, 'MDB2_Error')) { + echo('Connection to database successful.
'); + } else { + die('Error: connection to database failed. '.$conn->getMessage().'
'); + } + + $conn->setOption('debug', true); + $conn->setFetchMode(MDB2_FETCHMODE_ASSOC); + + $sql = "show tables"; + $res = $conn->query($sql); + if (is_a($res, 'MDB2_Error')) { + die('Error: show tables returned an error. '.$res->getMessage().'
'); + } + $tblCnt = 0; + while ($val = $res->fetchRow()) { + $tblCnt++; + } + if ($tblCnt > 0) { + echo("There are $tblCnt tables in database.
"); + } else { + echo('There are no tables in database. Execute step 1 - Create database structure.
'); + } -function setChange($sql) { - print "
".$sql."
"; - $mdb2 = getConnection(); - $affected = $mdb2->exec($sql); - if (is_a($affected, 'PEAR_Error')) { - print "error: ".$affected->getMessage()."
"; - } else { - print "successful update
\n"; - } + 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(); } - if ($_POST) { - print "Processing...
\n"; - - if ($_POST["crstructure"]) { - $sqlQuery = join("\n", file("mysql.sql")); - $sqlQuery = str_replace("TYPE=MyISAM","",$sqlQuery); - $queries = explode(";",$sqlQuery); - if (is_array($queries)) { - foreach ($queries as $query) { - $query = trim($query); - if (strlen($query)>0) { - setChange($query); - } - } - } - } - - if ($_POST["convert5to7"]) { - setChange("alter table `activity_log` CHANGE al_comment al_comment BLOB"); - 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 ($_POST) { + print "Processing...
\n"; + + if (array_key_exists('crstructure', $_POST)) { + $sqlQuery = join("\n", file("mysql.sql")); + $sqlQuery = str_replace("TYPE=MyISAM","",$sqlQuery); + $queries = explode(";",$sqlQuery); + if (is_array($queries)) { + foreach ($queries as $query) { + $query = trim($query); + if (strlen($query)>0) { + ttExecute($query); + } + } + } + } + + 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"; @@ -103,43 +253,42 @@ function setChange($sql) { } // Iterate through projects. while ($val = $res->fetchRow()) { - $project_id = $val['p_id']; - - // Get activity binds for project (old way). - // $sql = "select ab_id_a from activity_bind where ab_id_p = $project_id"; - $sql = "select ab_id_a, a_id, a_name from activity_bind - inner join activities on (ab_id_a = a_id) - where ab_id_p = $project_id - order by a_name"; - - $result = $mdb2->query($sql); - if (is_a($result, 'PEAR_Error')) { - die($result->getMessage()); - } - $activity_arr = array(); - while ($value = $result->fetchRow()) { - $activity_arr[] = $value['ab_id_a']; - } - $a_comma_separated = implode(",", $activity_arr); // This is a comma-separated list of associated activity ids. - - // Re-bind the project to activities (new way). - $sql = "update projects set p_activities = ".$mdb2->quote($a_comma_separated)." where p_id = $project_id"; - $affected = $mdb2->exec($sql); - if (is_a($affected, 'PEAR_Error')) { - die($affected->getMessage()); - } + $project_id = $val['p_id']; + + // Get activity binds for project (old way). + // $sql = "select ab_id_a from activity_bind where ab_id_p = $project_id"; + $sql = "select ab_id_a, a_id, a_name from activity_bind + inner join activities on (ab_id_a = a_id) + where ab_id_p = $project_id order by a_name"; + + $result = $mdb2->query($sql); + if (is_a($result, 'PEAR_Error')) { + die($result->getMessage()); + } + $activity_arr = array(); + while ($value = $result->fetchRow()) { + $activity_arr[] = $value['ab_id_a']; + } + $a_comma_separated = implode(",", $activity_arr); // This is a comma-separated list of associated activity ids. + + // Re-bind the project to activities (new way). + $sql = "update projects set p_activities = ".$mdb2->quote($a_comma_separated)." where p_id = $project_id"; + $affected = $mdb2->exec($sql); + if (is_a($affected, 'PEAR_Error')) { + die($affected->getMessage()); + } } } - - 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 @@ -154,127 +303,127 @@ function setChange($sql) { $show_pie = $val['u_show_pie']; $pie_mode = $val['u_pie_mode']; $lang = $val['u_lang']; - + $sql = "update companies set c_show_pie = $show_pie, c_pie_mode = $pie_mode, c_lang = ".$mdb2->quote($lang). " where c_id = $company_id"; - + $result = $mdb2->query($sql); if (is_a($result, 'PEAR_Error')) { die($result->getMessage()); } } } - - 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. + + // Update projects. $sql = "select p_id, p_manager_id from projects where team_id = 0 limit 1000"; $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) { @@ -285,7 +434,7 @@ function setChange($sql) { while ($val = $res->fetchRow()) { $project_id = $val['p_id']; $manager_id = $val['p_manager_id']; - + $sql = "select team_id from tt_users where id = $manager_id"; $res2 = $mdb2->query($sql); if (is_a($res2, 'PEAR_Error')) { @@ -293,7 +442,7 @@ function setChange($sql) { } $val2 = $res2->fetchRow(); $team_id = $val2['team_id']; - + if ($team_id) { $sql = "update projects set team_id = $team_id where p_id = $project_id"; $affected = $mdb2->exec($sql); @@ -304,8 +453,8 @@ function setChange($sql) { } } print "Updated $projects_updated projects...
\n"; - - // Update tasks. + + // Update tasks. $sql = "select a_id, a_manager_id from activities where team_id = 0 limit 1000"; $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) { @@ -316,7 +465,7 @@ function setChange($sql) { while ($val = $res->fetchRow()) { $task_id = $val['a_id']; $manager_id = $val['a_manager_id']; - + $sql = "select team_id from tt_users where id = $manager_id"; $res2 = $mdb2->query($sql); if (is_a($res2, 'PEAR_Error')) { @@ -324,19 +473,19 @@ function setChange($sql) { } $val2 = $res2->fetchRow(); $team_id = $val2['team_id']; - + if ($team_id) { $sql = "update activities set team_id = $team_id where a_id = $task_id"; $affected = $mdb2->exec($sql); if (is_a($affected, 'PEAR_Error')) { die($affected->getMessage()); } - $tasks_updated += $affected; + $tasks_updated += $affected; } } print "Updated $tasks_updated tasks...
\n"; - - // Update clients. + + // Update clients. $sql = "select clnt_id, clnt_id_um from clients where team_id = 0 limit 1000"; $res = $mdb2->query($sql); if (is_a($res, 'PEAR_Error')) { @@ -347,7 +496,7 @@ function setChange($sql) { while ($val = $res->fetchRow()) { $client_id = $val['clnt_id']; $manager_id = $val['clnt_id_um']; - + $sql = "select team_id from tt_users where id = $manager_id"; $res2 = $mdb2->query($sql); if (is_a($res2, 'PEAR_Error')) { @@ -355,267 +504,751 @@ function setChange($sql) { } $val2 = $res2->fetchRow(); $team_id = $val2['team_id']; - + if ($team_id) { $sql = "update clients set team_id = $team_id where clnt_id = $client_id"; $affected = $mdb2->exec($sql); if (is_a($affected, 'PEAR_Error')) { die($affected->getMessage()); } - $clients_updated += $affected; + $clients_updated += $affected; } } 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 blob"); - 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 (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 ($_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 (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"); } - - 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)"); - } - + // 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); if (is_a($res, 'PEAR_Error')) { die($res->getMessage()); } - + $clients_updated = 0; // Iterate through clients. while ($val = $res->fetchRow()) { $client_id = $val['id']; - + // Get projects binds for client. $sql = "select cpb.project_id from tt_client_project_binds cpb left join tt_projects p on (p.id = cpb.project_id) where cpb.client_id = $client_id order by p.name"; - + $result = $mdb2->query($sql); if (is_a($result, 'PEAR_Error')) die($result->getMessage()); - + $project_arr = array(); while ($value = $result->fetchRow()) { $project_arr[] = $value['project_id']; } $comma_separated = implode(',', $project_arr); // This is a comma-separated list of associated project ids. - + // Update the projects field. $sql = "update tt_clients set projects = ".$mdb2->quote($comma_separated)." where id = $client_id"; $affected = $mdb2->exec($sql); if (is_a($affected, 'PEAR_Error')) die($affected->getMessage()); - $clients_updated += $affected; + $clients_updated += $affected; } 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()); - + print "Updated $affected custom fields...
\n"; } // 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); if (is_a($affected, 'PEAR_Error')) die($affected->getMessage()); - + 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(); + + $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(); - $inactive_teams = ttTeamHelper::getInactiveTeams(); - - $count = count($inactive_teams); - print "$count inactive teams found...
\n"; + + $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_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"); + // 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"; + + print "Done.
\n"; } ?> @@ -624,52 +1257,69 @@ function setChange($sql) {

DB Install

- - - + + +
Create database structure (v1.9) -
(applies only to new installations, do not execute when updating)
Create database structure (v1.22.3) +
(applies only to new installations, do not execute when updating)

Updates

- - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Update database structure (v0.5 to v0.7)
Update database structure (v0.7 to v1.3.3)
Update database structure (v1.3.3 to v1.3.40)
Update database structure (v1.3.40 to v1.4.85)
Update database structure (v1.4.85 to v1.5.79)
Update database structure (v1.5.79 to v1.6)


Update database structure (v1.6 to v1.9)
Update database structure (v0.5 to v0.7)
Update database structure (v0.7 to v1.3.3)
Update database structure (v1.3.3 to v1.3.40)
Update database structure (v1.3.40 to v1.4.85)
Update database structure (v1.4.85 to v1.5.79)
Update database structure (v1.5.79 to v1.6)


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)

DB Maintenance

- - - + + +
Clean up DB from inactive teams
Clean up DB from inactive teams
- \ No newline at end of file + diff --git a/default.css b/default.css index da54b7e2c..d1a77ebd8 100644 --- a/default.css +++ b/default.css @@ -1,51 +1,657 @@ -/* -// +----------------------------------------------------------------------+ -// | 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; } a:hover { text-decoration: underline; } +html { + overflow-y: scroll; +} + 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; @@ -54,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 { @@ -112,9 +791,13 @@ select{ font-size: 10pt; font-family: verdana; } vertical-align: top; } +.midAligned { + text-align: center; +} + .sectionHeader { font-weight: bold; - border-bottom: 1px solid silver; + border-bottom: 1px solid lightgray; } .sectionHeaderNoBorder { @@ -122,14 +805,62 @@ select{ font-size: 10pt; font-family: verdana; } } .error { - font-weight: bold; color: red; } .info_message { - font-weight: bold; color: #0000c0; } -div#LoginAboutText { width:400px; } +.divider { + background-color: #efefef; +} + +div.table-divider { height: 30px; } + +.uncompleted-entry { + display: inline-block; + height: 8px; + width: 8px; + 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; +} + +/* Mobile styles */ +.mobile-table { + border: 0; + width: 100%; + border-spacing: 0; +} +.mobile-textarea { + width: 100%; + resize: vertical; + height: 5em; +} + +.mobile-input { + width: 100%; +} + +.mobile-table-details { + width: 100%; + table-layout: fixed; + overflow-wrap: break-word; + word-wrap: break-word; + border-spacing: 1px; + border: 0; +} + +.mobile-table-details td { + padding: 3px; +} 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 e8828e0c1..6dbf421a9 100644 --- a/expense_delete.php +++ b/expense_delete.php @@ -1,90 +1,64 @@ getParameter('id'); -$expense_item = ttExpenseHelper::getItem($cl_id, $user->getActiveUser()); +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. -// Prohibit deleting invoiced records. -if ($expense_item['invoice_id']) die($i18n->getKey('error.sys')); - -if ($request->getMethod() == 'POST') { - if ($request->getParameter('delete_button')) { // Delete button pressed. - - // Determine if it's okay to delete the record. +if ($request->isPost()) { + if ($request->getParameter('delete_button')) { // Delete button pressed. - // Determine lock date. - $lock_interval = $user->lock_interval; - $lockdate = 0; - if ($lock_interval > 0) { - $lockdate = new DateAndTime(); - $lockdate->decDay($lock_interval); - } - if ($lockdate) { - $item_date = new DateAndTime(DB_DATEFORMAT); - $item_date->parseVal($expense_item['date'], DB_DATEFORMAT); - if ($item_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_locked')); - } - - if ($errors->isEmpty()) { + // Determine if it is okay to delete the record. + $item_date = new ttDate($expense_item['date']); + if ($user->isDateLocked($item_date)) + $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 - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } if ($request->getParameter('cancel_button')) { // Cancel button pressed. - header('Location: expenses.php'); - exit(); + 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'))); +$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'); -?> \ No newline at end of file diff --git a/expense_edit.php b/expense_edit.php index f9c94dd64..98098a19f 100644 --- a/expense_edit.php +++ b/expense_edit.php @@ -1,218 +1,225 @@ 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()); +$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. -// Prohibit editing invoiced items. -if ($expense_item['invoice_id']) die($i18n->getKey('error.sys')); - -$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; -if ($request->getMethod() == 'POST') { +$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 && in_array('cl', explode(',', $user->plugins))) { - $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')) - )); +if (MODE_TIME == $trackingMode && $user->isPluginEnabled('cl')) { + $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')))); // 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. - if (in_array('cl', explode(',', $user->plugins))) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - // We need an array of assigned project ids to do some trimming. + $client_list = array(); + if ($user->isPluginEnabled('cl')) { + $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) { + $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; - } + $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')) - )); + 'empty'=>array(''=>$i18n->get('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)); +// If predefined expenses are configured, add controls to select an expense and quantity. +$predefined_expenses = ttGroupHelper::getPredefinedExpenses(); +if ($predefined_expenses) { + $form->addInput(array('type'=>'combobox', + 'onchange'=>'recalculateCost();', + 'name'=>'predefined_expense', + 'data'=>$predefined_expenses, + 'datakeys'=>array('id', 'name'), + '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','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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client) - $errors->add($i18n->getKey('error.client')); - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $errors->add($i18n->getKey('error.project')); - } - if (!ttValidString($cl_item_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.item')); - if (!ttValidFloat($cl_cost)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.cost')); - if (!ttValidDate($cl_date)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.date')); - - // Determine lock date. - $lock_interval = $user->lock_interval; - $lockdate = 0; - if ($lock_interval > 0) { - $lockdate = new DateAndTime(); - $lockdate->decDay($lock_interval); - } + 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)) - $errors->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')) { // We need to: - // 1) Prohibit updating locked entries (that are in locked interval). - // 2) Prohibit saving unlocked entries into locked interval. - + // 1) Prohibit updating locked entries (that are in locked range). + // 2) Prohibit saving unlocked entries into locked range. + // Now, step by step. - // 1) Prohibit updating locked entries. - if($lockdate && $item_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_locked')); - // 2) Prohibit saving completed unlocked entries into locked interval. - if($errors->isEmpty() && $lockdate && $new_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_locked')); - + // 1) Prohibit saving locked entries in any form. + if ($user->isDateLocked($item_date)) + $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->get('error.range_locked')); + // Now, an update. - if ($errors->isEmpty()) { - 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 ($err->no()) { + 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(); } } } - + // Save as new record. if ($request->getParameter('btn_copy')) { // We need to prohibit saving into locked interval. - if($lockdate && $new_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_locked')); - + if ($user->isDateLocked($new_date)) + $err->add($i18n->get('error.range_locked')); + // Now, a new insert. - if ($errors->isEmpty()) { - 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 ($err->no()) { + 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 - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } - + if ($request->getParameter('btn_delete')) { header("Location: expense_delete.php?id=$cl_id"); exit(); } -} // End of if ($request->getMethod() == "POST") +} // 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'); -?> \ No newline at end of file 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 9a08375bb..3cbbd8bec 100644 --- a/expenses.php +++ b/expenses.php @@ -1,112 +1,143 @@ 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'); +$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) { +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); + 'datakeys'=>array('id','name'))); + $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 && in_array('cl', explode(',', $user->plugins))) { - $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. - if (in_array('cl', explode(',', $user->plugins))) { - $active_clients = ttTeamHelper::getActiveClients($user->team_id, true); - // We need an array of assigned project ids to do some trimming. + $client_list = array(); + if ($user->isPluginEnabled('cl')) { + $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']; @@ -114,93 +145,109 @@ // 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; - } + $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')) - )); + 'empty'=>array(''=>$i18n->get('dropdown.select')))); + $largeScreenCalendarRowSpan += 2; } } -$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)); +// If predefined expenses are configured, add controls to select an expense and quantity. +$predefined_expenses = ttGroupHelper::getPredefinedExpenses(); +if ($predefined_expenses) { + $form->addInput(array('type'=>'combobox', + 'onchange'=>'recalculateCost();', + 'name'=>'predefined_expense', + 'data'=>$predefined_expenses, + 'datakeys'=>array('id', 'name'), + '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'=>'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'))); - -// Determine lock date. Time entries earlier than lock date cannot be created or modified. -$lock_interval = $user->lock_interval; -$lockdate = 0; -if ($lock_interval > 0) { - $lockdate = new DateAndTime(); - $lockdate->decDay($lock_interval); -} +$form->addInput(array('type'=>'submit','name'=>'btn_submit','onclick'=>'browser_today.value=get_date()','value'=>$i18n->get('button.submit'))); // Submit. -if ($request->getMethod() == 'POST') { +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 (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client) - $errors->add($i18n->getKey('error.client')); - if (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $errors->add($i18n->getKey('error.project')); - } - if (!ttValidString($cl_item_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.item')); - if (!ttValidFloat($cl_cost)) $errors->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)) - $errors->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 time entries in locked interval. - if($lockdate && $selected_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_locked')); - + + // Prohibit creating entries in locked range. + if ($user->isDateLocked($selected_date)) + $err->add($i18n->get('error.range_locked')); + // Insert record. - if ($errors->isEmpty()) { - 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))) { + if ($err->no()) { + $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 - $errors->add($i18n->getKey('error.db')); - } - } - else 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: 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'); -?> \ No newline at end of file diff --git a/export.php b/export.php index 4bb9db3d4..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->getMethod() == 'POST') { +if ($request->isPost()) { - $filename = 'team_data.xml'; + $filename = 'group_data.xml'; $mime_type = 'text/xml'; $compress = false; if ('bzip' == $cl_compression) { @@ -56,29 +32,28 @@ $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); - header('Expires: '.gmdate('D, d M Y H:i:s').' GMT'); - header('Content-Disposition: attachment; filename="'.$filename.'"'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Cache-Control: private', false); - + header('Pragma: public'); // This is needed for IE8 to download files over https. + header('Content-Type: '.$mime_type); + header('Expires: '.gmdate('D, d M Y H:i:s').' GMT'); + header('Content-Disposition: attachment; filename="'.$filename.'"'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Cache-Control: private', false); + if ($file_pointer = fopen($exportHelper->getFileName(), 'r')) { while ($data = fread($file_pointer, 4096)) { - echo $data; + echo $data; } fclose($file_pointer); unlink($exportHelper->getFileName()); } exit; } else - $errors->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'); -?> \ No newline at end of file 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/tt_logo.png b/images/tt_logo.png deleted file mode 100644 index 9eca180e6..000000000 Binary files a/images/tt_logo.png and /dev/null differ 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/img/logo.png b/img/logo.png new file mode 100644 index 000000000..23af4ab47 Binary files /dev/null and b/img/logo.png differ 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 47493db1e..b743b3f5a 100644 --- a/import.php +++ b/import.php @@ -1,56 +1,29 @@ addInput(array('type'=>'upload','name'=>'xmlfile','value'=>'browse','maxsize'=>16777216)); // 16 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 16M. -$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->getKey('button.import'))); +$form->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->get('button.import'))); -if ($request->getMethod() == 'POST') { - - $import = new ttImportHelper($errors); - $import->importXml(); - if ($errors->isEmpty()) - $messages->add($i18n->getKey('form.import.success')); -} +if ($request->isPost()) { + $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'); -?> \ No newline at end of file diff --git a/index.php b/index.php index a6eac171b..c74f2688b 100644 --- a/index.php +++ b/index.php @@ -1,67 +1,20 @@ isAuthenticated()) { - if ($user->isAdmin()) { - header('Location: admin_teams.php'); + if ($user->can('administer_site')) { + header('Location: admin_groups.php'); exit(); - } - else if ($user->isClient()) { + } elseif ($user->isClient()) { header('Location: reports.php'); - exit(); + 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) { -?> - - - - - - - - @@ -70,11 +23,6 @@ location.href = "time.php?date="+(new Date()).strftime(''); - - \ No newline at end of file diff --git a/initialize.php b/initialize.php index ce6acdfc4..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'); @@ -67,14 +53,13 @@ // If auth params are not defined (in config.php) - initialize with an empty array. if (!isset($GLOBALS['AUTH_MODULE_PARAMS']) || !is_array($GLOBALS['AUTH_MODULE_PARAMS'])) $GLOBALS['AUTH_MODULE_PARAMS'] = array(); - + // Smarty initialization. import('smarty.Smarty'); $smarty = new Smarty; $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,47 +115,29 @@ 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. -// 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'); $request = new ttHttpRequest(); import('form.ActionErrors'); -$errors = new ActionErrors(); -$messages = new ActionErrors(); +$err = new ActionErrors(); // Error messages for user. +$msg = new ActionErrors(); // Notification messages (not errrors) for user. // Create an instance of ttUser class. This gets us most of user details. 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); @@ -173,11 +150,11 @@ if (!$lang) { if (defined('LANG_DEFAULT')) $lang = LANG_DEFAULT; - + // If we still do not have the language get it from the browser. if (!$lang) { $lang = $i18n->getBrowserLanguage(); - + // Finally - English is the default. if (!$lang) { $lang = 'en'; @@ -187,17 +164,14 @@ // 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); -$smarty->assign('errors', $errors); -$smarty->assign('messages', $messages); +$smarty->assign('err', $err); +$smarty->assign('msg', $msg); + +// TODO: move this code out of here to the files that use it. -// TODO: move this code out of here to the files that use it. - // We use js/strftime.js to print dates in JavaScript (in DateField controls). // One of our date formats (%d.%m.%Y %a) prints a localized short weekday name (%a). // The init_js_date_locale function iniitializes Date.ext.locales array in js/strftime.js for our language @@ -248,4 +222,3 @@ function init_js_date_locale() };"; $smarty->assign('js_date_locale', $js); } -?> \ No newline at end of file diff --git a/invoice_add.php b/invoice_add.php index 4a25ddc18..e7018f009 100644 --- a/invoice_add.php +++ b/invoice_add.php @@ -1,45 +1,28 @@ isPluginEnabled('iv')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. -if ($request->getMethod() == 'POST') { +$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'); @@ -50,50 +33,52 @@ $form->addInput(array('type'=>'datefield','name'=>'date','size'=>'20','value'=>$cl_date)); // Dropdown for clients if the clients plugin is enabled. -if (in_array('cl', explode(',', $user->plugins))) { - $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')))); +if ($user->isPluginEnabled('cl')) { + $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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_number)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.invoice.number')); - if (!ttValidDate($cl_date)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.date')); - if (!$cl_client) $errors->add($i18n->getKey('error.client')); - if (!ttValidDate($cl_start)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.start_date')); - if (!ttValidDate($cl_finish)) $errors->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 ($errors->isEmpty()) { + if ($err->no()) { if (ttInvoiceHelper::getInvoiceByName($cl_number)) - $errors->add($i18n->getKey('error.invoice_exists')); - + $err->add($i18n->get('error.invoice_exists')); + if (!ttInvoiceHelper::invoiceableItemsExist($fields)) - $errors->add($i18n->getKey('error.no_invoiceable_items')); + $err->add($i18n->get('error.no_invoiceable_items')); } - if ($errors->isEmpty()) { - // Now we can go ahead and create our invoice. + if ($err->no()) { + // Now we can go ahead and create our invoice. if (ttInvoiceHelper::createInvoice($fields)) { header('Location: invoices.php'); exit(); + } else { + $err->add($i18n->get('error.db')); } - $errors->add($i18n->getKey('error.db')); } -} // post +} // 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'); -?> \ No newline at end of file diff --git a/invoice_delete.php b/invoice_delete.php index 9fb7c435b..5b473dfa0 100644 --- a/invoice_delete.php +++ b/invoice_delete.php @@ -1,74 +1,55 @@ 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->getMethod() == 'POST') { +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 - $errors->add($i18n->getKey('error.db')); + if (ttInvoiceHelper::delete($cl_invoice_id, $request->getParameter('delete_invoice_entries'))) { + header('Location: invoices.php'); + exit(); } else - $errors->add($i18n->getKey('error.db')); - } else if ($request->getParameter('btn_cancel')) { + $err->add($i18n->get('error.db')); + } elseif ($request->getParameter('btn_cancel')) { header('Location: invoices.php'); exit(); } -} // post +} // isPost $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'); -?> \ No newline at end of file diff --git a/invoice_send.php b/invoice_send.php index 1dd2323ab..5483a09b1 100644 --- a/invoice_send.php +++ b/invoice_send.php @@ -1,83 +1,64 @@ 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->getMethod() == 'POST') { +if ($request->isPost()) { $cl_receiver = trim($request->getParameter('receiver')); $cl_cc = trim($request->getParameter('cc')); $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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (!ttValidEmailList($cl_receiver)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.to')); - if (!ttValidEmailList($cl_cc, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.cc')); - if (!ttValidString($cl_subject)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.subject')); - if (!ttValidString($cl_comment, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.comment')); - - if ($errors->isEmpty()) { + 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); - - $body = ttInvoiceHelper::prepareInvoiceBody($cl_invoice_id, $cl_comment); - + $uc->setValue(SYSC_LAST_INVOICE_EMAIL, $cl_receiver); + $uc->setValue(SYSC_LAST_INVOICE_CC, $cl_cc); + + $body = ttInvoiceHelper::prepareInvoiceBody($cl_invoice_id, $cl_comment); + import('mail.Mailer'); $mailer = new Mailer(); $mailer->setCharSet(CHARSET); @@ -86,17 +67,18 @@ $mailer->setReceiver($cl_receiver); if (isset($cl_cc)) $mailer->setReceiverCC($cl_cc); - $mailer->setSendType(MAIL_MODE); + if (!empty($user->bcc_email)) + $mailer->setReceiverBCC($user->bcc_email); + $mailer->setMailMode(MAIL_MODE); if ($mailer->send($cl_subject, $body)) - $messages->add($i18n->getKey('form.mail.invoice_sent')); + $msg->add($i18n->get('form.mail.invoice_sent')); else - $errors->add($i18n->getKey('error.mail_send')); + $err->add($i18n->get('error.mail_send')); } -} +} // isPost -$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'); $smarty->display('index.tpl'); -?> \ No newline at end of file diff --git a/invoice_view.php b/invoice_view.php index a91ddf1b4..b1fcf91f9 100644 --- a/invoice_view.php +++ b/invoice_view.php @@ -1,90 +1,111 @@ 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) { - $tax_expenses = in_array('et', explode(',', $user->plugins)); +if ($tax_percent > 0) { + $tax_expenses = $user->isPluginEnabled('et'); foreach($invoice_items as $item) { - if ($item['type'] == 2 && !$tax_expenses) - continue; - $tax += round($item['cost'] * $tax_percent / 100, 2); + if ($item['type'] == 2 && !$tax_expenses) + continue; + $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++; -else if (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'); -?> \ No newline at end of file +$smarty->display('index.tpl'); diff --git a/invoices.php b/invoices.php index f495292e1..d34e1220e 100644 --- a/invoices.php +++ b/invoices.php @@ -1,45 +1,94 @@ 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'); -?> \ No newline at end of file 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 new file mode 100644 index 000000000..04c8d9419 --- /dev/null +++ b/locking.php @@ -0,0 +1,41 @@ +isPluginEnabled('lk')) { + header('Location: feature_disabled.php'); + exit(); +} + +$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->get('button.save'))); + +if ($request->isPost()) { + // Validate user input. + if (!ttValidCronSpec($cl_lock_spec)) $err->add($i18n->get('error.field'), $i18n->get('label.schedule')); + + if ($err->no()) { + if ($user->updateGroup(array('lock_spec' => $cl_lock_spec))) { + header('Location: group_edit.php'); + exit(); + } else { + $err->add($i18n->get('error.db')); + } + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$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 c9ddc8170..2d9dde044 100644 --- a/login.php +++ b/login.php @@ -1,96 +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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); - if (!ttValidString($cl_password)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - - if ($errors->isEmpty()) { - // Use the "limit" plugin if we have one. Ignore include errors. + 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'); - exit(); - } - else if ($user->isClient()) { - header('Location: reports.php'); - exit(); - } - 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 - $errors->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(!isTrue(MULTITEAM_MODE) && !ttTeamHelper::getTeams()) - $errors->add($i18n->getKey('error.no_teams')); + 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('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'); -?> \ No newline at end of file diff --git a/logout.php b/logout.php index adbcd54df..116bbe428 100644 --- a/logout.php +++ b/logout.php @@ -1,34 +1,9 @@ doLogout(); session_unset(); header('Location: login.php'); -?> \ No newline at end of file diff --git a/mobile/access_denied.php b/mobile/access_denied.php deleted file mode 100644 index 27ba6e84e..000000000 --- a/mobile/access_denied.php +++ /dev/null @@ -1,37 +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'); -?> \ No newline at end of file diff --git a/mobile/login.php b/mobile/login.php deleted file mode 100644 index f1c171a1b..000000000 --- a/mobile/login.php +++ /dev/null @@ -1,99 +0,0 @@ -getMethod() == 'POST') { - $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->getMethod() == 'POST') { - // Validate user input. - if (!ttValidString($cl_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); - if (!ttValidString($cl_password)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - - if ($errors->isEmpty()) { - - // 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'); - exit(); - } - else if ($user->isClient()) { - header('Location: ../reports.php'); - exit(); - } - else { - header('Location: time.php'); - exit(); - } - } else - $errors->add($i18n->getKey('error.auth')); - } -} - -if(!isTrue(MULTITEAM_MODE) && !ttTeamHelper::getTeams()) - $errors->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'); -?> \ No newline at end of file diff --git a/mobile/time.php b/mobile/time.php deleted file mode 100644 index 21c438511..000000000 --- a/mobile/time.php +++ /dev/null @@ -1,308 +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 (in_array('cf', explode(',', $user->plugins))) { - 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 (in_array('iv', explode(',', $user->plugins))) { - if ($request->getMethod() == 'POST') { - $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 && in_array('cl', explode(',', $user->plugins))) { - $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 (in_array('cl', explode(',', $user->plugins))) { - $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')) - )); - } -} - -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 (in_array('iv', explode(',', $user->plugins))) - $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)); - } else if ($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')) - )); - } -} - -// Determine lock date. Time entries earlier than lock date cannot be created or modified. -$lock_interval = $user->lock_interval; -$lockdate = 0; -if ($lock_interval > 0) { - $lockdate = new DateAndTime(); - $lockdate->decDay($lock_interval); -} - -// Submit. -if ($request->getMethod() == 'POST') { - if ($request->getParameter('btn_submit')) { - - // Validate user input. - if (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client) - $errors->add($i18n->getKey('error.client')); - if ($custom_fields) { - if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $errors->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) $errors->add($i18n->getKey('error.project')); - } - if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_task) $errors->add($i18n->getKey('error.task')); - } - if (!$cl_duration) { - if ('0' == $cl_duration) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); - else if ($cl_start || $cl_finish) { - if (!ttTimeHelper::isValidTime($cl_start)) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.start')); - if ($cl_finish) { - if (!ttTimeHelper::isValidTime($cl_finish)) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.finish')); - if (!ttTimeHelper::isValidInterval($cl_start, $cl_finish)) - $errors->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)) { - $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.start')); - $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish')); - } - if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type)) - $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration')); - } - } else { - if (!ttTimeHelper::isValidDuration($cl_duration)) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); - } - if (!ttValidString($cl_note, true)) $errors->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)) - $errors->add($i18n->getKey('error.future_date')); - } - - // Prohibit creating time entries in locked interval. - if($lockdate && $selected_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_locked')); - - // Prohibit creating another uncompleted record. - if ($errors->isEmpty()) { - if (($not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser())) && (($cl_finish == '') && ($cl_duration == ''))) - $errors->add($i18n->getKey('error.uncompleted_exists')." ".$i18n->getKey('error.goto_uncompleted').""); - } - - // Prohibit creating an overlapping record. - if ($errors->isEmpty()) { - if (ttTimeHelper::overlaps($user->getActiveUser(), $cl_date, $cl_start, $cl_finish)) - $errors->add($i18n->getKey('error.overlap')); - } - - if ($errors->isEmpty()) { - $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); - else if ($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(); - } - $errors->add($i18n->getKey('error.db')); - } - } -} - -$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'); -?> \ No newline at end of file diff --git a/mobile/time_delete.php b/mobile/time_delete.php deleted file mode 100644 index eb4ac2bf8..000000000 --- a/mobile/time_delete.php +++ /dev/null @@ -1,104 +0,0 @@ -team_id); -// } - -$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']); - -if ($request->getMethod() == 'POST') { - 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 lock date. - $lock_interval = $user->lock_interval; - $lockdate = 0; - if ($lock_interval > 0) { - $lockdate = new DateAndTime(); - $lockdate->decDay($lock_interval); - } - // Determine if the record is uncompleted. - $uncompleted = ($time_rec['duration'] == '0:00'); - - if($lockdate && $item_date->before($lockdate) && !$uncompleted) { - $errors->add($i18n->getKey('error.period_locked')); - } - - if ($errors->isEmpty()) { - - // Delete the record. - $result = ttTimeHelper::delete($cl_id, $user->getActiveUser()); - - if ($result) { - header('Location: time.php'); - exit(); - } else { - $errors->add($i18n->getKey('error.db')); - } - } - } - if ($request->getParameter('cancel_button')) { // Cancel button pressed. - header('Location: time.php'); - exit(); - } -} - -$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'); -?> \ No newline at end of file diff --git a/mobile/time_edit.php b/mobile/time_edit.php deleted file mode 100644 index 440aeefa8..000000000 --- a/mobile/time_edit.php +++ /dev/null @@ -1,350 +0,0 @@ -plugins))) { - 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->getMethod() == 'POST') { - $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 (in_array('iv', explode(',', $user->plugins))) - $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']; - else if ($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 (($cl_start == $cl_finish) && ($cl_duration == '0:00')) { - $cl_finish = ''; - $cl_duration = ''; - $messages->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 && in_array('cl', explode(',', $user->plugins))) { - $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 (in_array('cl', explode(',', $user->plugins))) { - $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')) - )); - } -} - -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','style'=>'width: 250px; height: 60px;','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)); - } else if ($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 (in_array('iv', explode(',', $user->plugins))) - $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->getMethod() == 'POST') { - - // Validate user input. - if (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client) - $errors->add($i18n->getKey('error.client')); - if ($custom_fields) { - if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $errors->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) $errors->add($i18n->getKey('error.project')); - } - if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_task) $errors->add($i18n->getKey('error.task')); - } - if (!$cl_duration) { - if ('0' == $cl_duration) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); - else if ($cl_start || $cl_finish) { - if (!ttTimeHelper::isValidTime($cl_start)) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.start')); - if ($cl_finish) { - if (!ttTimeHelper::isValidTime($cl_finish)) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.finish')); - if (!ttTimeHelper::isValidInterval($cl_start, $cl_finish)) - $errors->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)) { - $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.start')); - $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish')); - } - if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type)) - $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration')); - } - } else { - if (!ttTimeHelper::isValidDuration($cl_duration)) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); - } - if (!ttValidDate($cl_date)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.date')); - if (!ttValidString($cl_note, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.note')); - // Finished validating user input. - - // Determine lock date. - $lock_interval = $user->lock_interval; - $lockdate = 0; - if ($lock_interval > 0) { - $lockdate = new DateAndTime(); - $lockdate->decDay($lock_interval); - } - - // 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)) - $errors->add($i18n->getKey('error.future_date')); - } - - // Save record. - if ($request->getParameter('btn_save')) { - // We need to: - // 1) Prohibit saving locked time entries in any form. - // 2) Prohibit saving completed unlocked entries into locked interval. - // 3) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists. - - // Now, step by step. - if ($errors->isEmpty()) { - // 1) Prohibit saving locked time entries in any form. - if($lockdate && $item_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_locked')); - // 2) Prohibit saving completed unlocked entries into locked interval. - if($errors->isEmpty() && $lockdate && $new_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_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. - $errors->add($i18n->getKey('error.uncompleted_exists')." ".$i18n->getKey('error.goto_uncompleted').""); - } - } - } - - // Prohibit creating an overlapping record. - if ($errors->isEmpty()) { - if (ttTimeHelper::overlaps($user->getActiveUser(), $new_date->toString(DB_DATEFORMAT), $cl_start, $cl_finish, $cl_id)) - $errors->add($i18n->getKey('error.overlap')); - } - - // Now, an update. - if ($errors->isEmpty()) { - $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); - else if ($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(); - } -} // End of if ($request->getMethod() == 'POST') - -$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'); -?> \ No newline at end of file diff --git a/mysql.sql b/mysql.sql index 3e2832553..f614d0ac8 100644 --- a/mysql.sql +++ b/mysql.sql @@ -3,93 +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. +# 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_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 +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 - `locktime` int(4) default '0', # lock interval in days `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) - `plugins` varchar(255) default NULL, # a list of enabled plugins for team - `custom_logo` tinyint(4) default '0', # whether to use a custom logo or not - `status` tinyint(4) default '1', # team status + `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); # @@ -99,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`) ); @@ -112,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); # @@ -126,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` blob, # 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 ); @@ -185,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 - `users` text default NULL, # Comma-separated list of user ids. Nothing here means "all" users. + `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`) ); @@ -216,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`) ); @@ -231,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); # @@ -264,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 ); @@ -279,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`) ); @@ -293,8 +397,11 @@ CREATE TABLE `tt_custom_fields` ( # CREATE TABLE `tt_custom_field_options` ( `id` int(11) NOT NULL auto_increment, # option id - `field_id` int(11) NOT NULL, # custom field 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`) ); @@ -304,36 +411,216 @@ 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` varchar(255) 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); + + +# +# Structure for table tt_predefined_expenses. +# This table keeps names and costs for predefined expenses. +# +CREATE TABLE `tt_predefined_expenses` ( + `id` int(11) NOT NULL auto_increment, # predefined expense 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`) +); + + +# +# Structure for table tt_monthly_quotas. +# This table keeps monthly work hour quotas for groups. +# +CREATE TABLE `tt_monthly_quotas` ( + `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 + `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`) +); + +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 4d1b457c4..99602fe5d 100644 --- a/notification_add.php +++ b/notification_add.php @@ -1,30 +1,6 @@ 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(); -if ($request->getMethod() == 'POST') { - $cl_fav_report = trim($request->getParameter('fav_report')); +$cl_cron_spec = $cl_email = $cl_cc = $cl_subject = $cl_comment = $cl_report_condition = null; +if ($request->isPost()) { $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,43 +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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (!$cl_fav_report) $errors->add($i18n->getKey('error.report')); - if (!ttValidCronSpec($cl_cron_spec)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.cron_schedule')); - if (!ttValidEmail($cl_email)) $errors->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, time()); - if ($errors->isEmpty()) { - // Calculate next execution time. - $next = tdCron::getNextOccurrence($cl_cron_spec, mktime()); - 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 - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } -} // post +} // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -// $smarty->assign('onload', 'onLoad="document.clientForm.name.focus()"'); -$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'); -?> \ No newline at end of file diff --git a/notification_delete.php b/notification_delete.php index 62f11ef8e..93820427c 100644 --- a/notification_delete.php +++ b/notification_delete.php @@ -1,70 +1,55 @@ 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->getMethod() == 'POST') { +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 - $errors->add($i18n->getKey('error.db')); + if (ttNotificationHelper::delete($cl_notification_id)) { + header('Location: notifications.php'); + exit(); } else - $errors->add($i18n->getKey('error.db')); - } else if ($request->getParameter('btn_cancel')) { - header('Location: notifications.php'); - exit(); + $err->add($i18n->get('error.db')); + } elseif ($request->getParameter('btn_cancel')) { + header('Location: notifications.php'); + exit(); } -} // post - +} // isPost + $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'); -?> \ No newline at end of file diff --git a/notification_edit.php b/notification_edit.php index e029c6ca5..164127e93 100644 --- a/notification_edit.php +++ b/notification_edit.php @@ -1,30 +1,6 @@ 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->getMethod() == 'POST') { - $cl_fav_report = trim($request->getParameter('fav_report')); +if ($request->isPost()) { $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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (!$cl_fav_report) $errors->add($i18n->getKey('error.report')); - if (!ttValidCronSpec($cl_cron_spec)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.cron_schedule')); - if (!ttValidEmail($cl_email)) $errors->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 ($errors->isEmpty()) { - // Calculate next execution time. - $next = tdCron::getNextOccurrence($cl_cron_spec, mktime()); - + if ($err->no()) { + // Calculate next execution time. + $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 - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } -} // post +} // isPost $smarty->assign('forms', array($form->getName()=>$form->toArray())); -// $smarty->assign('onload', 'onLoad="document.clientForm.name.focus()"'); -$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'); -?> \ No newline at end of file diff --git a/notifications.php b/notifications.php index 5f2f6d2f7..5945320ef 100644 --- a/notifications.php +++ b/notifications.php @@ -1,57 +1,44 @@ 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'); -if ($request->getMethod() == 'POST') { +if ($request->isPost()) { if ($request->getParameter('btn_add')) { - // The Add button clicked. Redirect to notification_add.php page. - header('Location: notification_add.php'); - exit(); + // The Add button clicked. Redirect to notification_add.php page. + header('Location: notification_add.php'); + 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'); -?> \ No newline at end of file diff --git a/password_change.php b/password_change.php index 82ba11a4e..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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_password1)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $errors->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) - $errors->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 ($errors->isEmpty()) { - // Use the "limit" plugin if we have one. Ignore include errors. + 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. $cl_login = $user->login; // $cl_login is used in access_check.cpp. @include('plugins/limit/access_check.php'); - - ttUserHelper::setPassword($user_id, $cl_password1); - if ($auth->doLogin($user->login, $cl_password1)) { + ttUserHelper::setPassword($user_id, $cl_password1); - setcookie('tt_login', $user->login, time() + COOKIE_EXPIRE, '/'); - header('Location: time.php'); + if ($auth->doLogin($user->login, $cl_password1)) { + setcookie(LOGIN_COOKIE_NAME, $user->login, time() + COOKIE_EXPIRE, '/'); + // 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(); } else { - $errors->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'); -?> \ No newline at end of file diff --git a/password_reset.php b/password_reset.php index ccb196069..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->getMethod() == 'POST') { - $cl_login = $request->getParameter('login'); - +if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); + if (!ttValidString($cl_login)) $err->add($i18n->get('error.field'), $i18n->get('label.login')); - if ($errors->IsEmpty()) { - if (!ttUserHelper::getUserByLogin($cl_login)) { - // User with a specified login was not found. - // In this case, if login looks like email, try finding user by email. - if (ttValidEmail($cl_login)) { + if ($err->no()) { + if (!ttUserHelper::getUserByLogin($cl_login)) { + // User with a specified login was not found. + // In this case, if login looks like email, try finding user by email. + if (ttValidEmail($cl_login)) { $login = ttUserHelper::getUserByEmail($cl_login); if ($login) $cl_login = $login; else - $errors->add($i18n->getKey('error.no_login')); + $err->add($i18n->get('error.no_login')); } else - $errors->add($i18n->getKey('error.no_login')); - } + $err->add($i18n->get('error.no_login')); + } } - - if ($errors->IsEmpty()) { + + 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; if ($user->lang != $i18n->lang) { $user_i18n = new I18n(); - $user_i18n->load($user->lang); + $user_i18n->load($user->lang); } else $user_i18n = &$i18n; - + // Where do we email to? $receiver = null; if ($user->email) @@ -83,15 +67,16 @@ if (ttValidEmail($cl_login)) $receiver = $cl_login; else - $errors->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,22 +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; + $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; + + $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 - $pass_edit_url = $http.'://'.$_SERVER['HTTP_HOST'].'/password_change.php?ref='.$temp_ref; - - $sender->setSendType(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')); - } + $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'); -?> \ No newline at end of file diff --git a/phpinfo.php b/phpinfo.php deleted file mode 100644 index c1b42ed63..000000000 --- a/phpinfo.php +++ /dev/null @@ -1,4 +0,0 @@ - - diff --git a/plugins.php b/plugins.php new file mode 100644 index 000000000..a4372694a --- /dev/null +++ b/plugins.php @@ -0,0 +1,145 @@ +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 6eff5547e..b561af804 100644 --- a/plugins/CustomFields.class.php +++ b/plugins/CustomFields.class.php @@ -1,313 +1,583 @@ 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'=>''); - } + // Definitions of custom field types. + const ENTITY_TIME = 1; // Field is associated with time entries. + const ENTITY_USER = 2; // Field is associated with users. + const ENTITY_PROJECT = 3; // Field is associated with projects. + const TYPE_TEXT = 1; // A text field. + const TYPE_DROPDOWN = 2; // A dropdown field with pre-defined values. + + // TODO: replace $fields with entity-specific arrays: timeFields, userFields, etc. + + var $fields = array(); // Array of custom fields for group. + // Refactoring ongoing... + var $timeFields = null; + var $userFields = null; + var $projectFields = null; + + // Constructor. + function __construct() { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->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; + } + } } - - // If we have a dropdown obtain options for it. - if ((count($this->fields) > 0) && ($this->fields[0]['type'] == CustomFields::TYPE_DROPDOWN)) { - - $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']; + + function insert($log_id, $field_id, $option_id, $value) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_id; + + $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')); + } + + function update($log_id, $field_id, $option_id, $value) { + if (!$field_id) + return true; // Nothing to update. + + // 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); + } + + function delete($log_id) { + global $user; + $mdb2 = getConnection(); + + $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 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; } - } - - function insert($log_id, $field_id, $option_id, $value) { - - $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')); - } - - function update($log_id, $field_id, $option_id, $value) { - if (!$field_id) - return true; // Nothing to update. - - // Remove older custom field values, if any. - $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); - } - - function delete($log_id) { - - $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')); - } - - function get($log_id) { - $fields = array(); - - $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; + + // 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; + } + + // 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')) - return false; + + // updateOption updates option name. + static function updateOption($id, $option_name) { + global $user; + $mdb2 = getConnection(); + + $group_id = $user->getGroup(); + $org_id = $user->org_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; + + // Update entities_modified, too. + if (!ttGroupHelper::updateEntitiesModified()) + return false; + + return true; } - 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; + + // 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; + } + + // 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; + } + + // 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; - } - - // getOptiondName 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')); + } + + // 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; } - 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 users 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')); - } + + // 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); + } + } -?> \ No newline at end of file diff --git a/plugins/MonthlyQuota.class.php b/plugins/MonthlyQuota.class.php new file mode 100644 index 000000000..f37cd7885 --- /dev/null +++ b/plugins/MonthlyQuota.class.php @@ -0,0 +1,178 @@ +db = getConnection(); + global $user; + $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, $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 ($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); + 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. + // Month starts with 1 for January, not 0. + public function get($year, $month = null) { + if (is_null($month)){ + return $this->getMany($year); + } + return $this->getSingle($year, $month); + } + + // getSingle - obtains a quota for a single month. + private function getSingle($year, $month) { + $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; + } + + $row = $reader->fetchRow(); + if ($row) + 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->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 group. + private function getMany($year){ + $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')) { + return false; + } + + while ($val = $res->fetchRow()) { + $result[$val['month']] = $val['minutes']; + } + + return $result; + } + + // getNumWorkdays returns a number of work days in a given month. + private function getNumWorkdays($month, $year) { + + $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 new file mode 100644 index 000000000..08e395ce2 --- /dev/null +++ b/predefined_expense_add.php @@ -0,0 +1,49 @@ +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','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->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( + 'name' => $cl_name, + 'cost' => $cl_cost))) { + header('Location: predefined_expenses.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$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 new file mode 100644 index 000000000..e965b0be2 --- /dev/null +++ b/predefined_expense_delete.php @@ -0,0 +1,52 @@ +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. + + +$predefined_expense_to_delete = $predefined_expense['name']; + +$form = new Form('predefinedExpenseDeleteForm'); +$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::delete($predefined_expense_id)) { + header('Location: predefined_expenses.php'); + exit(); + } else + $err->add($i18n->get('error.db')); + } elseif ($request->getParameter('btn_cancel')) { + header('Location: predefined_expenses.php'); + exit(); + } +} // isPost + +$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->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 new file mode 100644 index 000000000..8f4750245 --- /dev/null +++ b/predefined_expense_edit.php @@ -0,0 +1,59 @@ +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 { + $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','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->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, + 'name' => $cl_name, + 'cost' => $cl_cost))) { + header('Location: predefined_expenses.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_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 new file mode 100644 index 000000000..9f94bd1cc --- /dev/null +++ b/predefined_expenses.php @@ -0,0 +1,37 @@ +isPluginEnabled('ex')) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. + +$form = new Form('predefinedExpensesForm'); + +if ($request->isPost()) { + if ($request->getParameter('btn_add')) { + // The Add button clicked. Redirect to predefined_expense_add.php page. + header('Location: predefined_expense_add.php'); + exit(); + } +} else { + $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->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 cdffd861e..3958a4018 100644 --- a/profile_edit.php +++ b/profile_edit.php @@ -1,45 +1,29 @@ 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; -if ($request->getMethod() == 'POST') { +$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')); if (!$auth->isPasswordExternal()) { @@ -47,225 +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_lock_interval = $request->getParameter('lock_interval'); - $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_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'); - } } 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_lock_interval = $user->lock_interval; - $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; - - // 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); + 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'=>'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,'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()')); - $form->addInput(array('type'=>'text','maxlength'=>'10','name'=>'lock_interval','value'=>$cl_lock_interval)); - // 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);')); +$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'))); - // 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)); +if ($request->isPost()) { + // Validate user input. + 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')); - // 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)); - - $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'=>'submit','name'=>'btn_save','value'=>$i18n->getKey('button.save'))); + // New login must be unique. + if ($cl_login != $current_login && ttUserHelper::getUserByLogin($cl_login)) + $err->add($i18n->get('error.user_exists')); -if ($request->getMethod() == 'POST') { - // Validate user input. - if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name')); - if ($can_change_login) { - if (!ttValidString($cl_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.login')); - - // New login must be unique. - if ($cl_login != $user->login && ttUserHelper::getUserByLogin($cl_login)) - $errors->add($i18n->getKey('error.user_exists')); - } if (!$auth->isPasswordExternal() && ($cl_password1 || $cl_password2)) { - if (!ttValidString($cl_password1)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $errors->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) - $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } - if (!ttValidEmail($cl_email, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - if ($user->canManageTeam()) { - if (!ttValidString($cl_team, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.team_name')); - if (!ttValidString($cl_address, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.address')); - if (!ttValidString($cl_currency, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.currency')); - if (!ttValidInteger($cl_lock_interval, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.lock_interval')); + $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 ($errors->isEmpty()) { - if ($cl_lock_interval == null || trim($cl_lock_interval) == '') - $cl_lock_interval = 0; - - $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'; - $plugins = trim($plugins, ','); - - $update_result = ttTeamHelper::update($user->team_id, array( - 'name' => $cl_team, - 'address' => $cl_address, - 'currency' => $cl_currency, - 'locktime' => $cl_lock_interval, - '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, - '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)); - } + if ($err->no()) { + $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 - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } -} // POST +} // 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'); -?> \ No newline at end of file diff --git a/project_add.php b/project_add.php index 2bdb77bbc..859fdc259 100644 --- a/project_add.php +++ b/project_add.php @@ -1,55 +1,59 @@ getTrackingMode() && MODE_PROJECTS_AND_TASKS != $user->getTrackingMode()) { + header('Location: feature_disabled.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); +} + +$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; -if ($request->getMethod() == 'POST') { +$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,39 +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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.description')); - - if ($errors->isEmpty()) { - if (!ttProjectHelper::getProjectByName($cl_name)) { - if (ttProjectHelper::insert(array( - 'team_id' => $user->team_id, - 'name' => $cl_name, + 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)) { + $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 - $errors->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 - $errors->add($i18n->getKey('error.project_exists')); + $err->add($i18n->get('error.object_exists')); } -} // post +} // 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'); -?> \ No newline at end of file diff --git a/project_delete.php b/project_delete.php index c49841b6d..4f4353b87 100644 --- a/project_delete.php +++ b/project_delete.php @@ -1,70 +1,51 @@ 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->getMethod() == 'POST') { +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 - $errors->add($i18n->getKey('error.db')); + if (ttProjectHelper::delete($cl_project_id)) { + header('Location: projects.php'); + exit(); } else - $errors->add($i18n->getKey('error.db')); - } else if ($request->getParameter('btn_cancel')) { - header('Location: projects.php'); - exit(); + $err->add($i18n->get('error.db')); + } elseif ($request->getParameter('btn_cancel')) { + header('Location: projects.php'); + exit(); } -} // post - +} // 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('title', $i18n->get('title.delete_project')); $smarty->assign('content_page_name', 'project_delete.tpl'); $smarty->display('index.tpl'); -?> \ No newline at end of file diff --git a/project_edit.php b/project_edit.php index 2726005c2..a0190ff83 100644 --- a/project_edit.php +++ b/project_edit.php @@ -1,134 +1,176 @@ 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->getMethod() == 'POST') { +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()); + // 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'))); - -if ($request->getMethod() == 'POST') { +$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)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.description')); - - if ($errors->isEmpty()) { + 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 - $errors->add($i18n->getKey('error.db')); - } else - $errors->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 - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $errors->add($i18n->getKey('error.project_exists')); + $err->add($i18n->get('error.object_exists')); } } -} // post +} // 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'); -?> \ No newline at end of file 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 8f1ce5c67..ed4d371df 100644 --- a/projects.php +++ b/projects.php @@ -1,50 +1,34 @@ 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'); -?> \ No newline at end of file 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 new file mode 100644 index 000000000..55214093f --- /dev/null +++ b/quotas.php @@ -0,0 +1,111 @@ +isPluginEnabled('mq')) { + header('Location: feature_disabled.php'); + exit(); +} + +// Start and end fallback values for the Year dropdown. +$yearStart = 2015; +$yearEnd = 2030; + +// If values are defined in config - use them. +if (defined('MONTHLY_QUOTA_YEAR_START')){ + $yearStart = (int)MONTHLY_QUOTA_YEAR_START; +} +if (defined('MONTHLY_QUOTA_YEAR_END')){ + $yearEnd = (int)MONTHLY_QUOTA_YEAR_END; +} + +// Create values for the Year dropdown. +$years = array(); +for ($i = $yearStart; $i <= $yearEnd; $i++) { + array_push($years, array('id'=>$i,'name'=>$i)); +} + +// Get selected year from url parameter. +$selectedYear = $request->getParameter('year'); +if (!$selectedYear or !ttValidInteger($selectedYear)){ + $selectedYear = date('Y'); +} else { + $selectedYear = (int) $selectedYear; +} + +// Months are zero indexed. +$months = $i18n->monthNames; + +$quota = new MonthlyQuota(); + +if ($request->isPost()){ + // 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')); + + 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]); + } + // 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++){ + $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(); + } + } +} + +// 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','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','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->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 67f7d108f..6f54ccf5b 100644 --- a/register.php +++ b/register.php @@ -1,110 +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', '$'); -if ($request->getMethod() == 'POST') { - $cl_team_name = trim($request->getParameter('team_name')); +$cl_group_name = $cl_currency = $cl_lang = $cl_manager_name = $cl_manager_login = +$cl_password1 = $cl_password2 = $cl_manager_email = ''; +if ($request->isPost()) { + $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)); -$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)); +// 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','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()) { + // Note: user input validation is done in ttRegistrator constructor. -if ($request->getMethod() == 'POST') { - // Validate user input. - if (!ttValidString($cl_team_name, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.team_name')); - if (!ttValidString($cl_currency, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.currency')); - if (!ttValidString($cl_manager_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_name')); - if (!ttValidString($cl_manager_login)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.manager_login')); - if (!ttValidString($cl_password1)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.confirm_password')); - if ($cl_password1 !== $cl_password2) - $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - if (!ttValidEmail($cl_manager_email, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); + // 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); - if ($errors->isEmpty()) { - 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 - $errors->add($i18n->getKey('error.db')); - } else - $errors->add($i18n->getKey('error.user_exists')); + // Create an instance of ttRegistrator class. + import('ttRegistrator'); + $registrator = new ttRegistrator($fields, $err); + $registrator->register(); + + if ($err->no()) { + // 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'); -?> \ No newline at end of file diff --git a/report.php b/report.php index 5ed7108d3..b8a549b69 100644 --- a/report.php +++ b/report.php @@ -1,123 +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 (in_array('cf', explode(',', $user->plugins))) { +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); } } -if ($request->getMethod() == 'POST') { - 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; +// 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()) { + + // 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(); + } + } +} // isPost + +$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); + +$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', ''); } - // Re-display this form. - header('Location: report.php'); - exit(); -} // post - -$group_by = $bean->getAttribute('group_by'); - -$report_items = ttReportHelper::getItems($bean); -if ('no_grouping' != $group_by) - $subtotals = ttReportHelper::getSubtotals($bean); -$totals = ttReportHelper::getTotals($bean); - -// 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 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)); +$totals = ttReportHelper::getTotals($options); + +// 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++; } } +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'); -?> \ No newline at end of file diff --git a/report_send.php b/report_send.php index 18e731e8a..b20dd6dfe 100644 --- a/report_send.php +++ b/report_send.php @@ -1,80 +1,58 @@ id); +$uc = new ttUserConfig(); -if ($request->getMethod() == 'POST') { +if ($request->isPost()) { $cl_receiver = trim($request->getParameter('receiver')); $cl_cc = trim($request->getParameter('cc')); $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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (!ttValidEmailList($cl_receiver)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.to')); - if (!ttValidEmailList($cl_cc, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.cc')); - if (!ttValidString($cl_subject)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.mail.subject')); - if (!ttValidString($cl_comment, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.comment')); - - if ($errors->isEmpty()) { + 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); - - // Obtain session bean with report attributes. - $bean = new ActionForm('reportBean', new Form('reportForm')); - // Prepare report body. - $body = ttReportHelper::prepareReportBody($bean, $cl_comment); - + $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($options, $cl_comment); + import('mail.Mailer'); $mailer = new Mailer(); $mailer->setCharSet(CHARSET); @@ -83,17 +61,18 @@ $mailer->setReceiver($cl_receiver); if (isset($cl_cc)) $mailer->setReceiverCC($cl_cc); - $mailer->setSendType(MAIL_MODE); + if (!empty($user->bcc_email)) + $mailer->setReceiverBCC($user->bcc_email); + $mailer->setMailMode(MAIL_MODE); if ($mailer->send($cl_subject, $body)) - $messages->add($i18n->getKey('form.mail.report_sent')); + $msg->add($i18n->get('form.mail.report_sent')); else - $errors->add($i18n->getKey('error.mail_send')); + $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'); $smarty->display('index.tpl'); -?> \ No newline at end of file diff --git a/reports.php b/reports.php index 9e18a0b03..aabeec08f 100644 --- a/reports.php +++ b/reports.php @@ -1,153 +1,206 @@ 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 (in_array('cf', explode(',', $user->plugins))) { +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 (in_array('cl', explode(',', $user->plugins)) && !($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. -} else if ($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 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. +$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')) )); } -// 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 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')) -)); - -$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. - else if ($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 ($users as $single_user) { - $user_list[$single_user['id']] = $single_user['name']; + 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) { @@ -155,180 +208,331 @@ } } } - $row_count = ceil(count($user_list)/3); + $row_count = is_array($user_list_active) ? ceil(count($user_list_active)/3) : 1; $form->addInput(array('type'=>'checkboxgroup', - 'name'=>'users', - 'data'=>$user_list, + 'name'=>'users_active', + 'data'=>$user_list_active, 'layout'=>'V', - 'groupin'=>$row_count, - 'style'=>'width: 100%;' - )); + 'groupin'=>$row_count)); + + 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) { + $assigned_projects[$single_user['id']][] = $single_project['id']; + } + } + } + $row_count = ceil(count($user_list_inactive)/3); + $form->addInput(array('type'=>'checkboxgroup', + 'name'=>'users_inactive', + 'data'=>$user_list_inactive, + 'layout'=>'V', + '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 (in_array('cl', explode(',', $user->plugins))) - $form->addInput(array('type'=>'checkbox','name'=>'chclient','data'=>1)); -if (($user->canManageTeam() || $user->isClient()) && in_array('iv', explode(',', $user->plugins))) - $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'); -if (in_array('cl', explode(',', $user->plugins)) && !($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['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->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']; + } } -$form->addInput(array('type'=>'combobox','onchange'=>'handleCheckboxes();','name'=>'group_by','data'=>$group_by_options)); -$form->addInput(array('type'=>'checkbox','name'=>'chtotalsonly','data'=>1)); +// 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']; + } +} +$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_generate','value'=>$i18n->getKey('button.generate'))); +$form->addInput(array('type'=>'submit','name'=>'btn_save','value'=>$i18n->get('button.save'))); + +$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... +// 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); - -if ($request->getMethod() == 'POST') { - 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)); - - // 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'))) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.reports.save_as_favorite')); - - if ($errors->isEmpty()) { - $id = ttFavReportHelper::saveReport($user->id, $bean); - if (!$id) - $errors->add($i18n->getKey('error.db')); - if ($errors->isEmpty()) { - $bean->setAttribute('favorite_report', $id); + $form->getElement('btn_delete')->setEnabled(false); + +if ($request->isPost()) { + // Verify bean. Do not proceed with bogus post data. + if (!ttReportHelper::verifyBean($bean)) $err->add($i18n->get('error.sys')); + + 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')); + } elseif ($bean->getAttribute('btn_save')) { + // User clicked the Save button. We need to save form options as new favorite report. + + // 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 ($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($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')) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.start_date')); - - $end_date = new DateAndTime($user->date_format, $bean->getAttribute('end_date')); - if ($end_date->isError() || !$bean->getAttribute('end_date')) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.end_date')); - - if ($start_date->compare($end_date) > 0) - $errors->add($i18n->getKey('error.interval'), $i18n->getKey('label.end_date'), $i18n->getKey('label.start_date')); - } - - $bean->saveBean(); - - if ($errors->isEmpty()) { - // Now we can go ahead and create a report. - header('Location: report.php'); - exit(); + $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()) { + $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'); -?> \ No newline at end of file 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 b37f10655..4a3353703 100644 --- a/task_add.php +++ b/task_add.php @@ -1,46 +1,28 @@ getTrackingMode()) { + header('Location: feature_disabled.php'); + exit(); +} +// End of access checks. -$projects = ttTeamHelper::getActiveProjects($user->team_id); +$projects = ttGroupHelper::getActiveProjects(); -if ($request->getMethod() == 'POST') { +$cl_name = $cl_description = ''; +if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_description = trim($request->getParameter('description')); $cl_projects = $request->getParameter('projects'); @@ -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'))); - -if ($request->getMethod() == 'POST') { +$form->addInput(array('type'=>'submit','name'=>'btn_submit','value'=>$i18n->get('button.add'))); + +if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $errors->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 ($errors->isEmpty()) { - if (!ttTaskHelper::getTaskByName($cl_name)) { + 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,15 +52,15 @@ header('Location: tasks.php'); exit(); } else - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $errors->add($i18n->getKey('error.task_exists')); - } -} // post + $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'); -?> \ No newline at end of file diff --git a/task_delete.php b/task_delete.php index 3329264b4..04008010f 100644 --- a/task_delete.php +++ b/task_delete.php @@ -1,70 +1,54 @@ 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->getMethod() == 'POST') { +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 - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $errors->add($i18n->getKey('error.db')); - } else if ($request->getParameter('btn_cancel')) { + $err->add($i18n->get('error.db')); + } elseif ($request->getParameter('btn_cancel')) { header('Location: tasks.php'); exit(); } -} // post +} // 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('title', $i18n->get('title.delete_task')); $smarty->assign('content_page_name', 'task_delete.tpl'); $smarty->display('index.tpl'); -?> \ No newline at end of file diff --git a/task_edit.php b/task_edit.php index 3a03d4281..1eb8c7f5b 100644 --- a/task_edit.php +++ b/task_edit.php @@ -1,78 +1,65 @@ getTrackingMode()) { + header('Location: feature_disabled.php'); + exit(); +} $cl_task_id = (int)$request->getParameter('id'); -$projects = ttTeamHelper::getActiveProjects($user->team_id); - -if ($request->getMethod() == 'POST') { +$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'))); - -if ($request->getMethod() == 'POST') { +$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)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.thing_name')); - if (!ttValidString($cl_description, true)) $errors->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 ($errors->isEmpty()) { - if ($request->getParameter('btn_save')) { + 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. @@ -85,15 +72,14 @@ header('Location: tasks.php'); exit(); } else - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $errors->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,15 +87,15 @@ header('Location: tasks.php'); exit(); } else - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $errors->add($i18n->getKey('error.task_exists')); + $err->add($i18n->get('error.object_exists')); } - } -} // post + } +} // 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'); -?> \ No newline at end of file diff --git a/tasks.php b/tasks.php index 6258831c8..13787a8ab 100644 --- a/tasks.php +++ b/tasks.php @@ -1,44 +1,53 @@ 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'); -?> \ No newline at end of file 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 e600968e4..ee90770b8 100644 --- a/time.php +++ b/time.php @@ -1,143 +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 (in_array('cf', explode(',', $user->plugins))) { +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_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 (in_array('iv', explode(',', $user->plugins))) { - if ($request->getMethod() == 'POST') { +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); + 'datakeys'=>array('id','name'))); + $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 && in_array('cl', explode(',', $user->plugins))) { - $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: 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', + '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. } -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 (in_array('cl', explode(',', $user->plugins))) { - $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']; @@ -145,149 +276,173 @@ // 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; - } + 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')) - )); + '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');")); -$form->addInput(array('type'=>'textarea','name'=>'note','style'=>'width: 600px; height: 40px;','value'=>$cl_note)); -$form->addInput(array('type'=>'calendar','name'=>'date','value'=>$cl_date)); // calendar -if (in_array('iv', explode(',', $user->plugins))) - $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)); - } else if ($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; } -// Determine lock date. Time entries earlier than lock date cannot be created or modified. -$lock_interval = $user->lock_interval; -$lockdate = 0; -if ($lock_interval > 0) { - $lockdate = new DateAndTime(); - $lockdate->decDay($lock_interval); +// 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. -if ($request->getMethod() == 'POST') { +// 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 (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client) - $errors->add($i18n->getKey('error.client')); - if ($custom_fields) { - if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $errors->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 (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $errors->add($i18n->getKey('error.project')); + if ($showProject) { + if (!$cl_project) $err->add($i18n->get('error.project')); } - if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_task) $errors->add($i18n->getKey('error.task')); - } - if (!$cl_duration) { - if ('0' == $cl_duration) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); - else if ($cl_start || $cl_finish) { + if ($showTask && $taskRequired) { + if (!$cl_task) $err->add($i18n->get('error.task')); + } + if ($cl_duration == null) { + if ($cl_start || $cl_finish) { if (!ttTimeHelper::isValidTime($cl_start)) - $errors->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)) - $errors->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)) - $errors->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)) { - $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.start')); - $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.finish')); - } - if ((TYPE_DURATION == $user->record_type) || (TYPE_ALL == $user->record_type)) - $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.duration')); + if ($showStart) { + $err->add($i18n->get('error.empty'), $i18n->get('label.start')); + $err->add($i18n->get('error.empty'), $i18n->get('label.finish')); + } + if ($showDuration) + $err->add($i18n->get('error.empty'), $i18n->get('label.duration')); } } else { - if (!ttTimeHelper::isValidDuration($cl_duration)) - $errors->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)) $errors->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)) - $errors->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 time entries in locked interval. - if($lockdate && $selected_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_locked')); - + + // Prohibit creating entries in locked range. + if ($user->isDateLocked($selected_date)) + $err->add($i18n->get('error.range_locked')); + // Prohibit creating another uncompleted record. - if ($errors->isEmpty()) { - if (($not_completed_rec = ttTimeHelper::getUncompleted($user->getActiveUser())) && (($cl_finish == '') && ($cl_duration == ''))) - $errors->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 ($errors->isEmpty()) { - if (ttTimeHelper::overlaps($user->getActiveUser(), $cl_date, $cl_start, $cl_finish)) - $errors->add($i18n->getKey('error.overlap')); - } + if ($err->no()) { + if (ttTimeHelper::overlaps($user_id, $cl_date, $cl_start, $cl_finish)) + $err->add($i18n->get('error.overlap')); + } // Insert record. - if ($errors->isEmpty()) { + if ($err->no()) { $id = ttTimeHelper::insert(array( 'date' => $cl_date, - 'user_id' => $user->getActiveUser(), 'client' => $cl_client, 'project' => $cl_project, 'task' => $cl_task, @@ -296,79 +451,88 @@ 'duration' => $cl_duration, '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); - else if ($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); + } + + // 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) { + + if ($id && $result && $err->no()) { header('Location: time.php'); exit(); } - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } - } - else if ($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()); - $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 + } elseif ($request->getParameter('btn_stop')) { + // Stop button pressed to finish an uncompleted record. + $record_id = $request->getParameter('record_id'); + $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_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'], - 'start'=>$record['start'], + 'id'=>$record['id'], + 'date'=>$record['date'], + 'client'=>$record['client_id'], + 'project'=>$record['project_id'], + 'task'=>$record['task_id'], + 'start'=>$record['start'], 'finish'=>$browser_time, 'note'=>$record['comment'], 'billable'=>$record['billable'])); if (!$res) - $errors->add($i18n->getKey('error.db')); - } else { + $err->add($i18n->get('error.db')); + } else { // Cannot complete, redirect for manual edit. header('Location: time_edit.php?id='.$record_id); - exit(); - } - } - else 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: 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'); -?> \ No newline at end of file diff --git a/time_delete.php b/time_delete.php index 7125a57ec..cd0af11ab 100644 --- a/time_delete.php +++ b/time_delete.php @@ -1,104 +1,73 @@ 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. -// Use Custom Fields plugin if we have one. -// if (file_exists("plugins/CustomFields.class.php")) { -// require_once("plugins/CustomFields.class.php"); -// $custom_fields = new CustomFields($user->team_id); -// } +$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; -$cl_id = $request->getParameter('id'); -$time_rec = ttTimeHelper::getRecord($cl_id, $user->getActiveUser()); +if ($request->isPost()) { + if ($request->getParameter('delete_button')) { // Delete button pressed. -// 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->getMethod() == 'POST') { - 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 lock date. - $lock_interval = $user->lock_interval; - $lockdate = 0; - if ($lock_interval > 0) { - $lockdate = new DateAndTime(); - $lockdate->decDay($lock_interval); - } + $item_date = new ttDate($time_rec['date']); + // Determine if the record is uncompleted. $uncompleted = ($time_rec['duration'] == '0:00'); - - if($lockdate && $item_date->before($lockdate) && !$uncompleted) { - $errors->add($i18n->getKey('error.period_locked')); - } - - if ($errors->isEmpty()) { - - // Delete the record. - $result = ttTimeHelper::delete($cl_id, $user->getActiveUser()); - if ($result) { + if ($user->isDateLocked($item_date) && !$uncompleted) + $err->add($i18n->get('error.range_locked')); + + if ($err->no()) { + // Delete the record. + if (ttTimeHelper::delete($cl_id)) { header('Location: time.php'); exit(); } else { - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } } if ($request->getParameter('cancel_button')) { // Cancel button pressed. - header('Location: time.php'); - exit(); + 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'))); +$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'); -?> \ No newline at end of file diff --git a/time_edit.php b/time_edit.php index 4c255dbef..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 (in_array('cf', explode(',', $user->plugins))) { +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()); +$item_date = new ttDate($time_rec['date']); +$confirm_save = $user->getConfigOption('confirm_save'); -// 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->getMethod() == 'POST') { - $cl_start = trim($request->getParameter('start')); - $cl_finish = trim($request->getParameter('finish')); - $cl_duration = trim($request->getParameter('duration')); +if ($request->isPost()) { + $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 (in_array('iv', explode(',', $user->plugins))) + 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,197 +96,244 @@ $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']; - else if ($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 (($cl_start == $cl_finish) && ($cl_duration == '0:00')) { - $cl_finish = ''; - $cl_duration = ''; - $messages->add($i18n->getKey('form.time_edit.uncompleted')); + if (strlen($cl_start) > 0 && $cl_start == $cl_finish && $cl_duration == '0:00') { + $cl_finish = null; + $cl_duration = null; + $msg->add($i18n->get('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 && in_array('cl', explode(',', $user->plugins))) { - $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: 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', + '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. } -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 (in_array('cl', explode(',', $user->plugins))) { - $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']; // 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) { + $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; - } + $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')) - )); + '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)); - } else if ($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 (in_array('iv', explode(',', $user->plugins))) - $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'))); -if ($request->getMethod() == 'POST') { +// 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 (in_array('cl', explode(',', $user->plugins)) && in_array('cm', explode(',', $user->plugins)) && !$cl_client) - $errors->add($i18n->getKey('error.client')); - if ($custom_fields) { - if (!ttValidString($cl_cf_1, !$custom_fields->fields[0]['required'])) $errors->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 (MODE_PROJECTS == $user->tracking_mode || MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_project) $errors->add($i18n->getKey('error.project')); + if ($showProject) { + if (!$cl_project) $err->add($i18n->get('error.project')); } - if (MODE_PROJECTS_AND_TASKS == $user->tracking_mode) { - if (!$cl_task) $errors->add($i18n->getKey('error.task')); + if ($showTask && $taskRequired) { + if (!$cl_task) $err->add($i18n->get('error.task')); } - if (!$cl_duration) { - if ('0' == $cl_duration) - $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.duration')); - else if ($cl_start || $cl_finish) { + if ($cl_duration == null) { + if ($cl_start || $cl_finish) { if (!ttTimeHelper::isValidTime($cl_start)) - $errors->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)) - $errors->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)) - $errors->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)) { - $errors->add($i18n->getKey('error.empty'), $i18n->getKey('label.start')); - $errors->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)) - $errors->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)) - $errors->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)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.date')); - if (!ttValidString($cl_note, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.note')); - // Finished validating user input. - - // Determine lock date. - $lock_interval = $user->lock_interval; - $lockdate = 0; - if ($lock_interval > 0) { - $lockdate = new DateAndTime(); - $lockdate->decDay($lock_interval); + 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)) - $errors->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. @@ -278,96 +342,96 @@ // 1) Prohibit saving locked time entries in any form. // 2) Prohibit saving completed unlocked entries into locked interval. // 3) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists. - + // Now, step by step. - if ($errors->isEmpty()) { - // 1) Prohibit saving locked time entries in any form. - if($lockdate && $item_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_locked')); - // 2) Prohibit saving completed unlocked entries into locked interval. - if($errors->isEmpty() && $lockdate && $new_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_locked')); + if ($err->no()) { + // 1) Prohibit saving locked entries in any form. + if ($user->isDateLocked($item_date)) + $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->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. - $errors->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 ($errors->isEmpty()) { - if (ttTimeHelper::overlaps($user->getActiveUser(), $new_date->toString(DB_DATEFORMAT), $cl_start, $cl_finish, $cl_id)) - $errors->add($i18n->getKey('error.overlap')); - } + if ($err->no()) { + if (ttTimeHelper::overlaps($user_id, $new_date->toString(), $cl_start, $cl_finish, $cl_id)) + $err->add($i18n->get('error.overlap')); + } // Now, an update. - if ($errors->isEmpty()) { + 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); - else if ($custom_fields->fields[0]['type'] == CustomFields::TYPE_DROPDOWN) - $res = $custom_fields->update($cl_id, $custom_fields->fields[0]['id'], $cl_cf_1, null); + 'id'=>$cl_id, + 'date'=>$new_date->toString(), + 'client'=>$cl_client, + 'project'=>$cl_project, + 'task'=>$cl_task, + 'start'=>$cl_start, + 'finish'=>$cl_finish, + 'duration'=>$cl_duration, + 'note'=>$cl_note, + '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 interval. + // 1) Prohibit saving into locked range. // 2) Prohibit saving uncompleted unlocked entries when another uncompleted entry exists. - + // Now, step by step. - if ($errors->isEmpty()) { - // 1) Prohibit saving into locked interval. - if($lockdate && $new_date->before($lockdate)) - $errors->add($i18n->getKey('error.period_locked')); + if ($err->no()) { + // 1) Prohibit saving into locked range. + if ($user->isDateLocked($new_date)) + $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. - $errors->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 ($errors->isEmpty()) { - if (ttTimeHelper::overlaps($user->getActiveUser(), $new_date->toString(DB_DATEFORMAT), $cl_start, $cl_finish)) - $errors->add($i18n->getKey('error.overlap')); + if ($err->no()) { + if (ttTimeHelper::overlaps($user_id, $new_date->toString(), $cl_start, $cl_finish)) + $err->add($i18n->get('error.overlap')); } // Now, a new insert. - if ($errors->isEmpty()) { - + 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, @@ -375,36 +439,45 @@ '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); - else if ($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(); } - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } } - + if ($request->getParameter('btn_delete')) { header("Location: time_delete.php?id=$cl_id"); exit(); } -} // End of if ($request->getMethod() == "POST") +} // 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'); -?> \ No newline at end of file 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 3d264e0df..0134885f8 100644 --- a/tofile.php +++ b/tofile.php @@ -1,65 +1,49 @@ plugins))) { +if ($user->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); -// At the moment, we distinguish 2 types of export to file: -// 1) export to xml -// 2) export to csv +// This file handles 2 types of export to a file: +// 1) xml +// 2) csv +// Export to pdf is handled separately in topdf.php. $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->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'); @@ -67,114 +51,149 @@ header('Cache-Control: no-store, no-cache, must-revalidate'); header('Cache-Control: post-check=0, pre-check=0', false); header('Cache-Control: private', false); - + // Handle 2 cases of possible exports individually. // 1) entries exported to xml if ('xml' == $type) { header('Content-Type: application/xml'); - header('Content-Disposition: attachment; filename="timesheet.xml"'); - + header('Content-Disposition: attachment; filename="'.$filename.'.xml"'); + 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()) - print $subtotal['cost']; - else - print $subtotal['expenses']; - print "]]>\n"; + print "\tcan('manage_invoices') || $user->isClient()) + print $subtotal['cost']; + else + print $subtotal['expenses']; + print "]]>\n"; } print "\n"; } } else { // Normal report. - foreach ($items as $item) { + foreach ($items as $item) { 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)) + $duration = $item['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()) - print $item['cost']; - else - print $item['expense']; - print "]]>\n"; + print "\tcan('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"; - } + } } - + print ""; -} +} // 2) entries exported to csv if ('csv' == $type) { header('Content-Type: application/csv'); - header('Content-Disposition: attachment; filename="timesheet.csv"'); - + header('Content-Disposition: attachment; filename="'.$filename.'.csv"'); + // Print UTF8 BOM first to identify encoding. $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. foreach ($subtotals as $subtotal) { 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.'"'; + 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'].'"'; @@ -183,46 +202,111 @@ } } 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()) - print ',"'.$item['cost'].'"'; - else - print ',"'.$item['expense'].'"'; + if ($user->can('manage_invoices') || $user->isClient()) { + if ($show_cost_per_hour) + print ',"'.$item['cost_per_hour'].'"'; + print ',"'.$item['cost'].'"'; + } 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"; } } } -?> \ No newline at end of file diff --git a/topdf.php b/topdf.php index 744ef4a2a..6393d5f55 100644 --- a/topdf.php +++ b/topdf.php @@ -1,40 +1,24 @@ plugins))) { +if ($user->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 = ''; $cur_grouped_by = ''; } +// Build a string to use as filename for the files being downloaded. +$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;"'; $styleSubtotal = 'style="background-color:#e0e0e0;"'; $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. @@ -113,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 .= ''; - + foreach ($items as $item) { // Print a subtotal for a block of grouped values. $cur_date = $item['date']; @@ -167,152 +179,298 @@ $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']; } - + // 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']; @@ -126,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']; @@ -145,21 +128,50 @@ // 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 .= $totals['expenses']; $html .= '
'; } + +// Output footer. +if (!defined('REPORT_FOOTER') || !(REPORT_FOOTER == false)) // By default we print it unless explicitely defined as false. + $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'); @@ -322,27 +480,27 @@ header('Cache-Control: private', false); header('Content-Type: application/pdf'); -header('Content-Disposition: attachment; filename="timesheet.pdf"'); +header('Content-Disposition: attachment; filename="'.$filename.'.pdf"'); // Beginning of TCPDF code here. // Extend TCPDF class so that we can use custom header and footer. -class MyyPDF extends TCPDF { +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. public function SetImageFile($imgFile) { $this->image_file = $imgFile; } - + // SetPageWord - sets page word for footer. public function SetPageWord($pageWord) { $this->page_word = $pageWord; } - + // Page header. public function Header() { // Print logo, which is the only element of our custom header. @@ -354,23 +512,21 @@ public function Footer() { // Position at 15 mm from bottom. $this->SetY(-15); // Set font. - $this->SetFont('helvetica', 'I', 8); + $this->SetFont('freeserif', 'I', 8); // Print localized page number. $this->Cell(0, 10, $this->page_word.' '.$this->getAliasNumPage().'/'.$this->getAliasNbPages(), 0, false, 'C', 0, '', 0, false, 'T', 'M'); } } // Create new PDF document. -$pdf = new MyyPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); +$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')); -// TODO: currently, we have problems rendering PDF in some languages such as Russian (headers, page word). -// Not sure how to fix it... One option is to switch to mPDF - consider. +$pdf->SetPageWord($i18n->get('label.page')); // Set document information. $pdf->SetCreator(PDF_CREATOR); @@ -393,15 +549,14 @@ public function Footer() { // Add a page. $pdf->AddPage(); -// Set font. -$pdf->SetFont('helvetica', '', 10); +// Set font (freeserif seems to work for all languages). +$pdf->SetFont('freeserif', '', 10); // helvetica here does not work for Russian. // Write HTML. $pdf->writeHTML($html, true, false, false, false, ''); // Close and output PDF document. // $pdf->Output('timesheet.pdf', 'I'); // This will display inline in browser. -$pdf->Output('timesheet.pdf', 'D'); // D is for downloads. +$pdf->Output($filename.'.pdf', 'D'); // D is for downloads. // End of of TCPDF code. -?> \ No newline at end of file diff --git a/user_add.php b/user_add.php index cf2fe76b5..e75e0901d 100644 --- a/user_add.php +++ b/user_add.php @@ -1,53 +1,43 @@ plugins))) - $clients = ttTeamHelper::getActiveClients($user->team_id); +$show_quota = $user->isPluginEnabled('mq'); +if ($user->isPluginEnabled('cl')) + $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->getMethod() == 'POST') { +if ($request->isPost()) { $cl_name = trim($request->getParameter('name')); $cl_login = trim($request->getParameter('login')); if (!$auth->isPasswordExternal()) { @@ -55,121 +45,180 @@ $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 - $errors->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'); -if (in_array('cl', explode(',', $user->plugins))) - $roles[ROLE_CLIENT] = $i18n->getKey('label.client'); -$form->addInput(array('type'=>'combobox','onchange'=>'handleClientControl()','name'=>'role','value'=>$cl_role,'data'=>$roles)); -if (in_array('cl', explode(',', $user->plugins))) - $form->addInput(array('type'=>'combobox','name'=>'client','value'=>$cl_client_id,'data'=>$clients,'datakeys'=>array('id', 'name'),'empty'=>array(''=>$i18n->getKey('dropdown.select')))); +$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')) + $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(); } } class RateCellRenderer extends DefaultCellRenderer { function render(&$table, $value, $row, $column, $selected = false) { global $assigned_projects; - $field = new FloatField('rate_'.$table->getValueAtName($row, 'id'), $table->getValueAtName($row, 'p_rate')); + $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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name')); - if (!ttValidString($cl_login)) $errors->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)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $errors->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) - $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } - if (!ttValidEmail($cl_email, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - if (!ttValidFloat($cl_rate, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.users.default_rate')); + $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')); + // 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 ($errors->isEmpty()) { + 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, + '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 - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $errors->add($i18n->getKey('error.user_exists')); + $err->add($i18n->get('error.user_exists')); } -} // post +} // 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'); -?> \ No newline at end of file diff --git a/user_delete.php b/user_delete.php index aed2746ad..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'))); - -if ($request->getMethod() == 'POST') { +$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,28 +37,27 @@ } // 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'); } else { - header('Location: users.php'); + header('Location: users.php'); } exit(); } else { - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('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('title', $i18n->get('title.delete_user')); $smarty->assign('content_page_name', 'user_delete.tpl'); $smarty->display('index.tpl'); -?> \ No newline at end of file diff --git a/user_edit.php b/user_edit.php index e51142e1c..0633e8a1f 100644 --- a/user_edit.php +++ b/user_edit.php @@ -1,70 +1,51 @@ 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. -if (in_array('cl', explode(',', $user->plugins))) - $clients = ttTeamHelper::getActiveClients($user->team_id); +$show_quota = $user->isPluginEnabled('mq'); +if ($user->isPluginEnabled('cl')) + $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); +} -if ($request->getMethod() == 'POST') { +$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')); if (!$auth->isPasswordExternal()) { @@ -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_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 - $errors->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,135 +111,177 @@ } $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'); -if (in_array('cl', explode(',', $user->plugins))) - $roles[ROLE_CLIENT] = $i18n->getKey('label.client'); -$form->addInput(array('type'=>'combobox','onchange'=>'handleClientControl()','name'=>'role','value'=>$cl_role,'data'=>$roles)); -if (in_array('cl', explode(',', $user->plugins))) - $form->addInput(array('type'=>'combobox','name'=>'client','value'=>$cl_client_id,'data'=>$clients,'datakeys'=>array('id', 'name'),'empty'=>array(''=>$i18n->getKey('dropdown.select')))); +$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')) + $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(); } } class RateCellRenderer extends DefaultCellRenderer { function render(&$table, $value, $row, $column, $selected = false) { global $assigned_projects; - $field = new FloatField('rate_'.$table->getValueAtName($row,'id'), $table->getValueAtName($row, 'p_rate')); + $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->getMethod() == 'POST') { +if ($request->isPost()) { // Validate user input. - if (!ttValidString($cl_name)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.person_name')); - if (!ttValidString($cl_login)) $errors->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)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.password')); - if (!ttValidString($cl_password2)) $errors->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) - $errors->add($i18n->getKey('error.not_equal'), $i18n->getKey('label.password'), $i18n->getKey('label.confirm_password')); - } - if (!ttValidEmail($cl_email, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('label.email')); - if (!ttValidFloat($cl_rate, true)) $errors->add($i18n->getKey('error.field'), $i18n->getKey('form.users.default_rate')); + $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')); + // 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 ($errors->isEmpty()) { + 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; - $fields['client_id'] = $cl_client_id; + 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. 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); - + // 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'); exit(); } - + header('Location: users.php'); exit(); } else - $errors->add($i18n->getKey('error.db')); + $err->add($i18n->get('error.db')); } else - $errors->add($i18n->getKey('error.user_exists')); + $err->add($i18n->get('error.user_exists')); } -} // post +} // 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'); -?> \ No newline at end of file diff --git a/users.php b/users.php index 9de72230f..38d4e1bc3 100644 --- a/users.php +++ b/users.php @@ -1,52 +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); +} + +$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 => $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'); -?> \ No newline at end of file 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');